summaryrefslogtreecommitdiffstats
path: root/media/webrtc/signaling/test
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /media/webrtc/signaling/test
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'media/webrtc/signaling/test')
-rw-r--r--media/webrtc/signaling/test/FakeIPC.cpp35
-rw-r--r--media/webrtc/signaling/test/FakeIPC.h22
-rw-r--r--media/webrtc/signaling/test/FakeLogging.h26
-rw-r--r--media/webrtc/signaling/test/FakeMediaStreams.h656
-rw-r--r--media/webrtc/signaling/test/FakeMediaStreamsImpl.h236
-rw-r--r--media/webrtc/signaling/test/FakePCObserver.h112
-rw-r--r--media/webrtc/signaling/test/common.build134
-rw-r--r--media/webrtc/signaling/test/jsep_session_unittest.cpp4235
-rw-r--r--media/webrtc/signaling/test/jsep_track_unittest.cpp1269
-rw-r--r--media/webrtc/signaling/test/mediaconduit_unittests.cpp1091
-rw-r--r--media/webrtc/signaling/test/mediapipeline_unittest.cpp720
-rw-r--r--media/webrtc/signaling/test/moz.build33
-rw-r--r--media/webrtc/signaling/test/sdp_file_parser.cpp85
-rw-r--r--media/webrtc/signaling/test/sdp_unittests.cpp5377
-rw-r--r--media/webrtc/signaling/test/signaling_unittests.cpp4851
15 files changed, 18882 insertions, 0 deletions
diff --git a/media/webrtc/signaling/test/FakeIPC.cpp b/media/webrtc/signaling/test/FakeIPC.cpp
new file mode 100644
index 000000000..767082c29
--- /dev/null
+++ b/media/webrtc/signaling/test/FakeIPC.cpp
@@ -0,0 +1,35 @@
+/* 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 "FakeIPC.h"
+#include <unistd.h>
+
+// The implementations can't be in the .h file for some annoying reason
+
+/* static */ void
+PlatformThread:: YieldCurrentThread()
+{
+ sleep(1);
+}
+
+namespace base {
+
+void AtExitManager::RegisterCallback(AtExitCallbackType func, void* param)
+{
+}
+
+}
+
+// see atomicops_internals_x86_gcc.h
+// This cheats to get the unittests to build
+
+struct AtomicOps_x86CPUFeatureStruct {
+ bool field1;
+ bool field2;
+};
+
+struct AtomicOps_x86CPUFeatureStruct AtomicOps_Internalx86CPUFeatures = {
+ false,
+ false,
+};
diff --git a/media/webrtc/signaling/test/FakeIPC.h b/media/webrtc/signaling/test/FakeIPC.h
new file mode 100644
index 000000000..e13fc271d
--- /dev/null
+++ b/media/webrtc/signaling/test/FakeIPC.h
@@ -0,0 +1,22 @@
+/* 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/. */
+
+#ifndef FAKE_IPC_H_
+#define FAKE_IPC_H_
+#include <unistd.h>
+
+class PlatformThread {
+public:
+ static void YieldCurrentThread();
+};
+
+namespace base {
+class AtExitManager {
+public:
+ typedef void (*AtExitCallbackType)(void*);
+
+ static void RegisterCallback(AtExitCallbackType func, void* param);
+};
+}
+#endif
diff --git a/media/webrtc/signaling/test/FakeLogging.h b/media/webrtc/signaling/test/FakeLogging.h
new file mode 100644
index 000000000..2620ddd01
--- /dev/null
+++ b/media/webrtc/signaling/test/FakeLogging.h
@@ -0,0 +1,26 @@
+/* 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/. */
+
+#ifndef FakeLogging_h
+#define FakeLogging_h
+
+namespace mozilla {
+namespace detail {
+void log_print(const PRLogModuleInfo* aModule,
+ LogLevel aLevel,
+ const char* aFmt, ...)
+ {
+ // copied from Logging.cpp:#48-53
+ va_list ap;
+ va_start(ap, aFmt);
+ char* buff = PR_vsmprintf(aFmt, ap);
+ PR_LogPrint("%s", buff);
+ PR_smprintf_free(buff);
+ va_end(ap);
+ }
+
+}
+}
+
+#endif
diff --git a/media/webrtc/signaling/test/FakeMediaStreams.h b/media/webrtc/signaling/test/FakeMediaStreams.h
new file mode 100644
index 000000000..117d26905
--- /dev/null
+++ b/media/webrtc/signaling/test/FakeMediaStreams.h
@@ -0,0 +1,656 @@
+/* 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/. */
+
+#ifndef FAKE_MEDIA_STREAM_H_
+#define FAKE_MEDIA_STREAM_H_
+
+#include <set>
+#include <string>
+#include <sstream>
+#include <vector>
+
+#include "nsNetCID.h"
+#include "nsITimer.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIComponentManager.h"
+#include "nsIComponentRegistrar.h"
+#include "nsISupportsImpl.h"
+#include "nsServiceManagerUtils.h"
+
+// #includes from MediaStream.h
+#include "mozilla/Mutex.h"
+#include "AudioSegment.h"
+#include "MediaSegment.h"
+#include "StreamTracks.h"
+#include "VideoSegment.h"
+#include "nsTArray.h"
+#include "nsIRunnable.h"
+#include "nsISupportsImpl.h"
+
+class nsPIDOMWindowInner;
+
+namespace mozilla {
+ class MediaStreamGraphImpl;
+ class MediaSegment;
+ class PeerConnectionImpl;
+ class PeerConnectionMedia;
+ class RemoteSourceStreamInfo;
+};
+
+
+namespace mozilla {
+
+class MediaStreamGraph;
+
+static MediaStreamGraph* gGraph;
+
+struct AudioChannel {
+ enum {
+ Normal
+ };
+};
+
+enum MediaStreamGraphEvent : uint32_t;
+enum TrackEventCommand : uint32_t;
+
+class MediaStreamGraph {
+public:
+ // Keep this in sync with the enum in MediaStreamGraph.h
+ enum GraphDriverType {
+ AUDIO_THREAD_DRIVER,
+ SYSTEM_THREAD_DRIVER,
+ OFFLINE_THREAD_DRIVER
+ };
+ static MediaStreamGraph* GetInstance(GraphDriverType aDriverType,
+ uint32_t aType) {
+ if (gGraph) {
+ return gGraph;
+ }
+ gGraph = new MediaStreamGraph();
+ return gGraph;
+ }
+};
+}
+
+class Fake_VideoSink {
+public:
+ Fake_VideoSink() {}
+ virtual void SegmentReady(mozilla::MediaSegment* aSegment) = 0;
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Fake_VideoSink)
+protected:
+ virtual ~Fake_VideoSink() {}
+};
+
+class Fake_MediaStream;
+class Fake_SourceMediaStream;
+
+class Fake_MediaStreamListener
+{
+protected:
+ virtual ~Fake_MediaStreamListener() {}
+
+public:
+ virtual void NotifyQueuedTrackChanges(mozilla::MediaStreamGraph* aGraph, mozilla::TrackID aID,
+ mozilla::StreamTime aTrackOffset,
+ mozilla::TrackEventCommand aTrackEvents,
+ const mozilla::MediaSegment& aQueuedMedia,
+ Fake_MediaStream* aInputStream,
+ mozilla::TrackID aInputTrackID) {}
+ virtual void NotifyPull(mozilla::MediaStreamGraph* aGraph, mozilla::StreamTime aDesiredTime) = 0;
+ virtual void NotifyQueuedAudioData(mozilla::MediaStreamGraph* aGraph, mozilla::TrackID aID,
+ mozilla::StreamTime aTrackOffset,
+ const mozilla::AudioSegment& aQueuedMedia,
+ Fake_MediaStream* aInputStream,
+ mozilla::TrackID aInputTrackID) {}
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Fake_MediaStreamListener)
+};
+
+class Fake_DirectMediaStreamListener : public Fake_MediaStreamListener
+{
+protected:
+ virtual ~Fake_DirectMediaStreamListener() {}
+
+public:
+ virtual void NotifyRealtimeData(mozilla::MediaStreamGraph* graph, mozilla::TrackID tid,
+ mozilla::StreamTime offset,
+ const mozilla::MediaSegment& media) = 0;
+};
+
+class Fake_MediaStreamTrackListener
+{
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Fake_MediaStreamTrackListener)
+
+protected:
+ virtual ~Fake_MediaStreamTrackListener() {}
+
+public:
+ virtual void NotifyQueuedChanges(mozilla::MediaStreamGraph* aGraph,
+ mozilla::StreamTime aTrackOffset,
+ const mozilla::MediaSegment& aQueuedMedia) = 0;
+};
+
+class Fake_DirectMediaStreamTrackListener : public Fake_MediaStreamTrackListener
+{
+protected:
+ virtual ~Fake_DirectMediaStreamTrackListener() {}
+
+public:
+ virtual void NotifyRealtimeTrackData(mozilla::MediaStreamGraph* aGraph,
+ mozilla::StreamTime aTrackOffset,
+ const mozilla::MediaSegment& aMedia) = 0;
+ enum class InstallationResult {
+ STREAM_NOT_SUPPORTED,
+ SUCCESS
+ };
+ virtual void NotifyDirectListenerInstalled(InstallationResult aResult) = 0;
+ virtual void NotifyDirectListenerUninstalled() = 0;
+};
+
+class Fake_MediaStreamVideoSink : public Fake_DirectMediaStreamTrackListener{
+public:
+ Fake_MediaStreamVideoSink() {}
+
+ void NotifyQueuedChanges(mozilla::MediaStreamGraph* aGraph,
+ mozilla::StreamTime aTrackOffset,
+ const mozilla::MediaSegment& aQueuedMedia) override {}
+
+ void NotifyRealtimeTrackData(mozilla::MediaStreamGraph* aGraph,
+ mozilla::StreamTime aTrackOffset,
+ const mozilla::MediaSegment& aMedia) override {}
+ void NotifyDirectListenerInstalled(InstallationResult aResult) override {}
+ void NotifyDirectListenerUninstalled() override {}
+
+ virtual void SetCurrentFrames(const mozilla::VideoSegment& aSegment) {};
+ virtual void ClearFrames() {};
+
+protected:
+ virtual ~Fake_MediaStreamVideoSink() {}
+};
+
+// Note: only one listener supported
+class Fake_MediaStream {
+ protected:
+ virtual ~Fake_MediaStream() { Stop(); }
+
+ struct BoundTrackListener
+ {
+ BoundTrackListener(Fake_MediaStreamTrackListener* aListener,
+ mozilla::TrackID aTrackID)
+ : mListener(aListener), mTrackID(aTrackID) {}
+ RefPtr<Fake_MediaStreamTrackListener> mListener;
+ mozilla::TrackID mTrackID;
+ };
+
+ public:
+ Fake_MediaStream () : mListeners(), mTrackListeners(), mMutex("Fake MediaStream") {}
+
+ static uint32_t GraphRate() { return 16000; }
+
+ void AddListener(Fake_MediaStreamListener *aListener) {
+ mozilla::MutexAutoLock lock(mMutex);
+ mListeners.insert(aListener);
+ }
+
+ void RemoveListener(Fake_MediaStreamListener *aListener) {
+ mozilla::MutexAutoLock lock(mMutex);
+ mListeners.erase(aListener);
+ }
+
+ void AddTrackListener(Fake_MediaStreamTrackListener *aListener,
+ mozilla::TrackID aTrackID) {
+ mozilla::MutexAutoLock lock(mMutex);
+ mTrackListeners.push_back(BoundTrackListener(aListener, aTrackID));
+ }
+
+ void RemoveTrackListener(Fake_MediaStreamTrackListener *aListener,
+ mozilla::TrackID aTrackID) {
+ mozilla::MutexAutoLock lock(mMutex);
+ for (std::vector<BoundTrackListener>::iterator it = mTrackListeners.begin();
+ it != mTrackListeners.end(); ++it) {
+ if (it->mListener == aListener && it->mTrackID == aTrackID) {
+ mTrackListeners.erase(it);
+ return;
+ }
+ }
+ }
+
+ void NotifyPull(mozilla::MediaStreamGraph* graph,
+ mozilla::StreamTime aDesiredTime) {
+
+ mozilla::MutexAutoLock lock(mMutex);
+ std::set<RefPtr<Fake_MediaStreamListener>>::iterator it;
+ for (it = mListeners.begin(); it != mListeners.end(); ++it) {
+ (*it)->NotifyPull(graph, aDesiredTime);
+ }
+ }
+
+ virtual Fake_SourceMediaStream *AsSourceStream() { return nullptr; }
+
+ virtual mozilla::MediaStreamGraphImpl *GraphImpl() { return nullptr; }
+ virtual nsresult Start() { return NS_OK; }
+ virtual nsresult Stop() { return NS_OK; }
+ virtual void StopStream() {}
+
+ virtual void Periodic() {}
+
+ double StreamTimeToSeconds(mozilla::StreamTime aTime);
+ mozilla::StreamTime
+ TicksToTimeRoundDown(mozilla::TrackRate aRate, mozilla::TrackTicks aTicks);
+ mozilla::TrackTicks TimeToTicksRoundUp(mozilla::TrackRate aRate,
+ mozilla::StreamTime aTime);
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Fake_MediaStream);
+
+ protected:
+ std::set<RefPtr<Fake_MediaStreamListener>> mListeners;
+ std::vector<BoundTrackListener> mTrackListeners;
+ mozilla::Mutex mMutex; // Lock to prevent the listener list from being modified while
+ // executing Periodic().
+};
+
+class Fake_MediaPeriodic : public nsITimerCallback {
+public:
+ explicit Fake_MediaPeriodic(Fake_MediaStream *aStream) : mStream(aStream),
+ mCount(0) {}
+ void Detach() {
+ mStream = nullptr;
+ }
+
+ int GetTimesCalled() { return mCount; }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITIMERCALLBACK
+
+protected:
+ virtual ~Fake_MediaPeriodic() {}
+
+ Fake_MediaStream *mStream;
+ int mCount;
+};
+
+
+class Fake_SourceMediaStream : public Fake_MediaStream {
+ public:
+ Fake_SourceMediaStream() : mSegmentsAdded(0),
+ mDesiredTime(0),
+ mPullEnabled(false),
+ mStop(false),
+ mPeriodic(new Fake_MediaPeriodic(this)) {}
+
+ enum {
+ ADDTRACK_QUEUED = 0x01 // Queue track add until FinishAddTracks()
+ };
+
+ void AddVideoSink(const RefPtr<Fake_VideoSink>& aSink) {
+ mSink = aSink;
+ }
+
+ void AddTrack(mozilla::TrackID aID, mozilla::StreamTime aStart,
+ mozilla::MediaSegment* aSegment, uint32_t aFlags = 0) {
+ delete aSegment;
+ }
+ void AddAudioTrack(mozilla::TrackID aID, mozilla::TrackRate aRate, mozilla::StreamTime aStart,
+ mozilla::AudioSegment* aSegment, uint32_t aFlags = 0) {
+ delete aSegment;
+ }
+ void FinishAddTracks() {}
+ void EndTrack(mozilla::TrackID aID) {}
+
+ bool AppendToTrack(mozilla::TrackID aID, mozilla::MediaSegment* aSegment,
+ mozilla::MediaSegment *aRawSegment) {
+ return AppendToTrack(aID, aSegment);
+ }
+
+ bool AppendToTrack(mozilla::TrackID aID, mozilla::MediaSegment* aSegment) {
+ bool nonZeroSample = false;
+ MOZ_ASSERT(aSegment);
+ if(aSegment->GetType() == mozilla::MediaSegment::AUDIO) {
+ //On audio segment append, we verify for validity
+ //of the audio samples.
+ mozilla::AudioSegment* audio =
+ static_cast<mozilla::AudioSegment*>(aSegment);
+ mozilla::AudioSegment::ChunkIterator iter(*audio);
+ while(!iter.IsEnded()) {
+ mozilla::AudioChunk& chunk = *(iter);
+ MOZ_ASSERT(chunk.mBuffer);
+ const int16_t* buf =
+ static_cast<const int16_t*>(chunk.mChannelData[0]);
+ for(int i=0; i<chunk.mDuration; i++) {
+ if(buf[i]) {
+ //atleast one non-zero sample found.
+ nonZeroSample = true;
+ break;
+ }
+ }
+ //process next chunk
+ iter.Next();
+ }
+ if(nonZeroSample) {
+ //we increment segments count if
+ //atleast one non-zero samples was found.
+ ++mSegmentsAdded;
+ }
+ } else {
+ //in the case of video segment appended, we just increase the
+ //segment count.
+ if (mSink.get()) {
+ mSink->SegmentReady(aSegment);
+ }
+ ++mSegmentsAdded;
+ }
+ return true;
+ }
+
+ void AdvanceKnownTracksTime(mozilla::StreamTime aKnownTime) {}
+
+ void SetPullEnabled(bool aEnabled) {
+ mPullEnabled = aEnabled;
+ }
+ void AddDirectListener(Fake_MediaStreamListener* aListener) {}
+ void RemoveDirectListener(Fake_MediaStreamListener* aListener) {}
+
+ //Don't pull anymore data,if mStop is true.
+ virtual void StopStream() {
+ mStop = true;
+ }
+
+ virtual Fake_SourceMediaStream *AsSourceStream() { return this; }
+
+ virtual nsresult Start();
+ virtual nsresult Stop();
+
+ virtual void Periodic();
+
+ virtual int GetSegmentsAdded() {
+ return mSegmentsAdded;
+ }
+
+ protected:
+ int mSegmentsAdded;
+ uint64_t mDesiredTime;
+ bool mPullEnabled;
+ bool mStop;
+ RefPtr<Fake_MediaPeriodic> mPeriodic;
+ RefPtr<Fake_VideoSink> mSink;
+ nsCOMPtr<nsITimer> mTimer;
+};
+
+class Fake_DOMMediaStream;
+
+class Fake_MediaStreamTrackSource
+{
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Fake_MediaStreamTrackSource)
+
+protected:
+ virtual ~Fake_MediaStreamTrackSource() {}
+};
+
+class Fake_MediaStreamTrack
+{
+ friend class mozilla::PeerConnectionImpl;
+ friend class mozilla::PeerConnectionMedia;
+ friend class mozilla::RemoteSourceStreamInfo;
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Fake_MediaStreamTrack)
+
+ Fake_MediaStreamTrack(bool aIsVideo, Fake_DOMMediaStream* aOwningStream) :
+ mIsVideo (aIsVideo),
+ mOwningStream (aOwningStream),
+ mTrackID(mIsVideo ? 1 : 0)
+ {
+ static size_t counter = 0;
+ std::ostringstream os;
+ os << counter++;
+ mID = os.str();
+ }
+
+ std::string GetId() const { return mID; }
+ void AssignId(const std::string& id) { mID = id; }
+ mozilla::MediaStreamGraphImpl* GraphImpl() { return nullptr; }
+ const Fake_MediaStreamTrack* AsVideoStreamTrack() const
+ {
+ return mIsVideo? this : nullptr;
+ }
+ const Fake_MediaStreamTrack* AsAudioStreamTrack() const
+ {
+ return mIsVideo? nullptr : this;
+ }
+ uint32_t typeSize () const
+ {
+ return sizeof(Fake_MediaStreamTrack);
+ }
+ const char* typeName () const
+ {
+ return "Fake_MediaStreamTrack";
+ }
+ void AddListener(Fake_MediaStreamTrackListener *aListener);
+ void RemoveListener(Fake_MediaStreamTrackListener *aListener);
+ void AddDirectListener(Fake_DirectMediaStreamTrackListener *aListener)
+ {
+ AddListener(aListener);
+ aListener->NotifyDirectListenerInstalled(
+ Fake_DirectMediaStreamTrackListener::InstallationResult::STREAM_NOT_SUPPORTED);
+ }
+ void RemoveDirectListener(Fake_DirectMediaStreamTrackListener *aListener)
+ {
+ RemoveListener(aListener);
+ }
+
+ class PrincipalChangeObserver
+ {
+ public:
+ virtual void PrincipalChanged(Fake_MediaStreamTrack* aMediaStreamTrack) = 0;
+ };
+ void AddPrincipalChangeObserver(void* ignoredObserver) {}
+ void RemovePrincipalChangeObserver(void* ignoredObserver) {}
+
+private:
+ ~Fake_MediaStreamTrack() {}
+
+ const bool mIsVideo;
+ Fake_DOMMediaStream* mOwningStream;
+ mozilla::TrackID mTrackID;
+ std::string mID;
+};
+
+class Fake_DOMMediaStream : public nsISupports
+{
+ friend class mozilla::PeerConnectionMedia;
+protected:
+ virtual ~Fake_DOMMediaStream() {
+ // Note: memory leak
+ mMediaStream->Stop();
+ }
+
+public:
+ explicit Fake_DOMMediaStream(Fake_MediaStream *stream = nullptr)
+ : mMediaStream(stream ? stream : new Fake_MediaStream())
+ , mVideoTrack(new Fake_MediaStreamTrack(true, this))
+ , mAudioTrack(new Fake_MediaStreamTrack(false, this))
+ {
+ static size_t counter = 0;
+ std::ostringstream os;
+ os << counter++;
+ mID = os.str();
+ }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ static already_AddRefed<Fake_DOMMediaStream>
+ CreateSourceStreamAsInput(nsPIDOMWindowInner* aWindow,
+ mozilla::MediaStreamGraph* aGraph,
+ uint32_t aHintContents = 0) {
+ Fake_SourceMediaStream *source = new Fake_SourceMediaStream();
+
+ RefPtr<Fake_DOMMediaStream> ds = new Fake_DOMMediaStream(source);
+ ds->SetHintContents(aHintContents);
+
+ return ds.forget();
+ }
+
+ virtual void Stop() {} // Really DOMLocalMediaStream
+
+ virtual bool AddDirectListener(Fake_MediaStreamListener *aListener) { return false; }
+ virtual void RemoveDirectListener(Fake_MediaStreamListener *aListener) {}
+
+ Fake_MediaStream *GetInputStream() { return mMediaStream; }
+ Fake_MediaStream *GetOwnedStream() { return mMediaStream; }
+ Fake_MediaStream *GetPlaybackStream() { return mMediaStream; }
+ Fake_MediaStream *GetStream() { return mMediaStream; }
+ std::string GetId() const { return mID; }
+ void AssignId(const std::string& id) { mID = id; }
+ Fake_MediaStreamTrack* GetTrackById(const std::string& aId)
+ {
+ if (mHintContents & HINT_CONTENTS_AUDIO) {
+ if (mAudioTrack && mAudioTrack->GetId() == aId) {
+ return mAudioTrack;
+ }
+ }
+ if (mHintContents & HINT_CONTENTS_VIDEO) {
+ if (mVideoTrack && mVideoTrack->GetId() == aId) {
+ return mVideoTrack;
+ }
+ }
+ return nullptr;
+ }
+ Fake_MediaStreamTrack* GetOwnedTrackById(const std::string& aId)
+ {
+ return GetTrackById(aId);
+ }
+
+ // Hints to tell the SDP generator about whether this
+ // MediaStream probably has audio and/or video
+ typedef uint8_t TrackTypeHints;
+ enum {
+ HINT_CONTENTS_AUDIO = 0x01,
+ HINT_CONTENTS_VIDEO = 0x02
+ };
+ uint32_t GetHintContents() const { return mHintContents; }
+ void SetHintContents(uint32_t aHintContents) { mHintContents = aHintContents; }
+
+ void
+ GetTracks(nsTArray<RefPtr<Fake_MediaStreamTrack> >& aTracks)
+ {
+ GetAudioTracks(aTracks);
+ GetVideoTracks(aTracks);
+ }
+
+ void GetAudioTracks(nsTArray<RefPtr<Fake_MediaStreamTrack> >& aTracks)
+ {
+ if (mHintContents & HINT_CONTENTS_AUDIO) {
+ aTracks.AppendElement(mAudioTrack);
+ }
+ }
+
+ void
+ GetVideoTracks(nsTArray<RefPtr<Fake_MediaStreamTrack> >& aTracks)
+ {
+ if (mHintContents & HINT_CONTENTS_VIDEO) {
+ aTracks.AppendElement(mVideoTrack);
+ }
+ }
+
+ bool
+ HasTrack(const Fake_MediaStreamTrack& aTrack) const
+ {
+ return ((mHintContents & HINT_CONTENTS_AUDIO) && aTrack.AsAudioStreamTrack()) ||
+ ((mHintContents & HINT_CONTENTS_VIDEO) && aTrack.AsVideoStreamTrack());
+ }
+
+ bool
+ OwnsTrack(const Fake_MediaStreamTrack& aTrack) const
+ {
+ return HasTrack(aTrack);
+ }
+
+ void SetTrackEnabled(mozilla::TrackID aTrackID, bool aEnabled) {}
+
+ void AddTrackInternal(Fake_MediaStreamTrack* aTrack) {}
+
+ already_AddRefed<Fake_MediaStreamTrack>
+ CreateDOMTrack(mozilla::TrackID aTrackID, mozilla::MediaSegment::Type aType,
+ Fake_MediaStreamTrackSource* aSource)
+ {
+ switch(aType) {
+ case mozilla::MediaSegment::AUDIO: {
+ return do_AddRef(mAudioTrack);
+ }
+ case mozilla::MediaSegment::VIDEO: {
+ return do_AddRef(mVideoTrack);
+ }
+ default: {
+ MOZ_CRASH("Unkown media type");
+ }
+ }
+ }
+
+private:
+ RefPtr<Fake_MediaStream> mMediaStream;
+
+ // tells the SDP generator about whether this
+ // MediaStream probably has audio and/or video
+ uint32_t mHintContents;
+ RefPtr<Fake_MediaStreamTrack> mVideoTrack;
+ RefPtr<Fake_MediaStreamTrack> mAudioTrack;
+
+ std::string mID;
+};
+
+class Fake_MediaStreamBase : public Fake_MediaStream {
+ public:
+ Fake_MediaStreamBase() : mPeriodic(new Fake_MediaPeriodic(this)) {}
+
+ virtual nsresult Start();
+ virtual nsresult Stop();
+
+ virtual int GetSegmentsAdded() {
+ return mPeriodic->GetTimesCalled();
+ }
+
+ private:
+ nsCOMPtr<nsITimer> mTimer;
+ RefPtr<Fake_MediaPeriodic> mPeriodic;
+};
+
+
+class Fake_AudioStreamSource : public Fake_MediaStreamBase {
+ public:
+ Fake_AudioStreamSource() : Fake_MediaStreamBase(),
+ mCount(0),
+ mStop(false) {}
+ //Signaling Agent indicates us to stop generating
+ //further audio.
+ void StopStream() {
+ mStop = true;
+ }
+ virtual void Periodic();
+ int mCount;
+ bool mStop;
+};
+
+class Fake_VideoStreamSource : public Fake_MediaStreamBase {
+ public:
+ Fake_VideoStreamSource() : Fake_MediaStreamBase() {}
+};
+
+
+namespace mozilla {
+typedef Fake_MediaStream MediaStream;
+typedef Fake_SourceMediaStream SourceMediaStream;
+typedef Fake_MediaStreamListener MediaStreamListener;
+typedef Fake_DirectMediaStreamListener DirectMediaStreamListener;
+typedef Fake_MediaStreamTrackListener MediaStreamTrackListener;
+typedef Fake_DirectMediaStreamTrackListener DirectMediaStreamTrackListener;
+typedef Fake_DOMMediaStream DOMMediaStream;
+typedef Fake_DOMMediaStream DOMLocalMediaStream;
+typedef Fake_MediaStreamVideoSink MediaStreamVideoSink;
+
+namespace dom {
+typedef Fake_MediaStreamTrack MediaStreamTrack;
+typedef Fake_MediaStreamTrackSource MediaStreamTrackSource;
+}
+}
+
+#endif
diff --git a/media/webrtc/signaling/test/FakeMediaStreamsImpl.h b/media/webrtc/signaling/test/FakeMediaStreamsImpl.h
new file mode 100644
index 000000000..da0e81416
--- /dev/null
+++ b/media/webrtc/signaling/test/FakeMediaStreamsImpl.h
@@ -0,0 +1,236 @@
+/* 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/. */
+
+#ifndef FAKE_MEDIA_STREAMIMPL_H_
+#define FAKE_MEDIA_STREAMIMPL_H_
+
+#include "FakeMediaStreams.h"
+
+#include "nspr.h"
+#include "nsError.h"
+
+void LogTime(AsyncLatencyLogger::LatencyLogIndex index, uint64_t b, int64_t c) {}
+void LogLatency(AsyncLatencyLogger::LatencyLogIndex index, uint64_t b, int64_t c) {}
+
+static const int AUDIO_BUFFER_SIZE = 1600;
+static const int NUM_CHANNELS = 2;
+static const int GRAPH_RATE = 16000;
+
+NS_IMPL_ISUPPORTS0(Fake_DOMMediaStream)
+
+// Fake_MediaStream
+double Fake_MediaStream::StreamTimeToSeconds(mozilla::StreamTime aTime) {
+ return static_cast<double>(aTime)/GRAPH_RATE;
+}
+
+mozilla::TrackTicks Fake_MediaStream::TimeToTicksRoundUp(mozilla::TrackRate aRate,
+ mozilla::StreamTime aTime) {
+ return (aTime * aRate) / GRAPH_RATE;
+}
+
+mozilla::StreamTime
+Fake_MediaStream::TicksToTimeRoundDown(mozilla::TrackRate aRate,
+ mozilla::TrackTicks aTicks) {
+ return aTicks * GRAPH_RATE / aRate;
+}
+
+// Fake_SourceMediaStream
+nsresult Fake_SourceMediaStream::Start() {
+ mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+ if (!mTimer) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mTimer->InitWithCallback(mPeriodic, 100, nsITimer::TYPE_REPEATING_SLACK);
+
+ return NS_OK;
+}
+
+nsresult Fake_SourceMediaStream::Stop() {
+ mozilla::MutexAutoLock lock(mMutex);
+ if (mTimer)
+ mTimer->Cancel();
+ mPeriodic->Detach();
+ return NS_OK;
+}
+
+void Fake_SourceMediaStream::Periodic() {
+ mozilla::MutexAutoLock lock(mMutex);
+ // Pull more audio-samples iff pulling is enabled
+ // and we are not asked by the signaling agent to stop
+ //pulling data.
+ if (mPullEnabled && !mStop) {
+ // 100 ms matches timer interval and AUDIO_BUFFER_SIZE @ 16000 Hz
+ mDesiredTime += 100;
+ for (std::set<RefPtr<Fake_MediaStreamListener>>::iterator it =
+ mListeners.begin(); it != mListeners.end(); ++it) {
+ (*it)->NotifyPull(nullptr, TicksToTimeRoundDown(1000 /* ms per s */,
+ mDesiredTime));
+ }
+ }
+}
+
+// Fake_MediaStreamTrack
+void Fake_MediaStreamTrack::AddListener(Fake_MediaStreamTrackListener *aListener)
+{
+ mOwningStream->GetInputStream()->AddTrackListener(aListener, mTrackID);
+}
+void Fake_MediaStreamTrack::RemoveListener(Fake_MediaStreamTrackListener *aListener)
+{
+ mOwningStream->GetInputStream()->RemoveTrackListener(aListener, mTrackID);
+}
+
+// Fake_MediaStreamBase
+nsresult Fake_MediaStreamBase::Start() {
+ mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+ if (!mTimer) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mTimer->InitWithCallback(mPeriodic, 100, nsITimer::TYPE_REPEATING_SLACK);
+
+ return NS_OK;
+}
+
+nsresult Fake_MediaStreamBase::Stop() {
+ // Lock the mutex so that we know that after this
+ // has returned, periodic will not be firing again
+ // and so it's safe to destruct.
+ mozilla::MutexAutoLock lock(mMutex);
+ mTimer->Cancel();
+
+ return NS_OK;
+}
+
+// Fake_AudioStreamSource
+void Fake_AudioStreamSource::Periodic() {
+ mozilla::MutexAutoLock lock(mMutex);
+ //Are we asked to stop pumping audio samples ?
+ if(mStop) {
+ return;
+ }
+ //Generate Signed 16 Bit Audio samples
+ RefPtr<mozilla::SharedBuffer> samples =
+ mozilla::SharedBuffer::Create(AUDIO_BUFFER_SIZE * NUM_CHANNELS * sizeof(int16_t));
+ int16_t* data = reinterpret_cast<int16_t *>(samples->Data());
+ for(int i=0; i<(1600*2); i++) {
+ //saw tooth audio sample
+ data[i] = ((mCount % 8) * 4000) - (7*4000)/2;
+ mCount++;
+ }
+
+ mozilla::AudioSegment segment;
+ AutoTArray<const int16_t *,1> channels;
+ channels.AppendElement(data);
+ segment.AppendFrames(samples.forget(),
+ channels,
+ AUDIO_BUFFER_SIZE,
+ PRINCIPAL_HANDLE_NONE);
+
+ for(std::set<RefPtr<Fake_MediaStreamListener>>::iterator it = mListeners.begin();
+ it != mListeners.end(); ++it) {
+ (*it)->NotifyQueuedTrackChanges(nullptr, // Graph
+ 0, // TrackID
+ 0, // Offset TODO(ekr@rtfm.com) fix
+ static_cast<mozilla::TrackEventCommand>(0), // ???
+ segment,
+ nullptr, // Input stream
+ -1); // Input track id
+ }
+ for(std::vector<BoundTrackListener>::iterator it = mTrackListeners.begin();
+ it != mTrackListeners.end(); ++it) {
+ it->mListener->NotifyQueuedChanges(nullptr, // Graph
+ 0, // Offset TODO(ekr@rtfm.com) fix
+ segment);
+ }
+}
+
+
+// Fake_MediaPeriodic
+NS_IMPL_ISUPPORTS(Fake_MediaPeriodic, nsITimerCallback)
+
+NS_IMETHODIMP
+Fake_MediaPeriodic::Notify(nsITimer *timer) {
+ if (mStream)
+ mStream->Periodic();
+ ++mCount;
+ return NS_OK;
+}
+
+
+#if 0
+#define WIDTH 320
+#define HEIGHT 240
+#define RATE USECS_PER_S
+#define USECS_PER_S 1000000
+#define FPS 10
+
+NS_IMETHODIMP
+Fake_VideoStreamSource::Notify(nsITimer* aTimer)
+{
+#if 0
+ mozilla::layers::BufferRecycleBin bin;
+
+ RefPtr<mozilla::layers::PlanarYCbCrImage> image = new
+ mozilla::layers::PlanarYCbCrImage(&bin);
+
+ const uint8_t lumaBpp = 8;
+ const uint8_t chromaBpp = 4;
+
+ int len = ((WIDTH * HEIGHT) * 3 / 2);
+ uint8_t* frame = (uint8_t*) PR_Malloc(len);
+ memset(frame, 0x80, len); // Gray
+
+ mozilla::layers::PlanarYCbCrData data;
+ data.mYChannel = frame;
+ data.mYSize = mozilla::gfx::IntSize(WIDTH, HEIGHT);
+ data.mYStride = WIDTH * lumaBpp / 8.0;
+ data.mCbCrStride = WIDTH * chromaBpp / 8.0;
+ data.mCbChannel = frame + HEIGHT * data.mYStride;
+ data.mCrChannel = data.mCbChannel + HEIGHT * data.mCbCrStride / 2;
+ data.mCbCrSize = mozilla::gfx::IntSize(WIDTH / 2, HEIGHT / 2);
+ data.mPicX = 0;
+ data.mPicY = 0;
+ data.mPicSize = mozilla::gfx::IntSize(WIDTH, HEIGHT);
+ data.mStereoMode = mozilla::layers::StereoMode::MONO;
+
+ mozilla::VideoSegment segment;
+ segment.AppendFrame(image.forget(), USECS_PER_S / FPS,
+ mozilla::gfx::IntSize(WIDTH, HEIGHT));
+
+ // TODO(ekr@rtfm.com): are we leaking?
+#endif
+
+ return NS_OK;
+}
+
+
+#if 0
+// Fake up buffer recycle bin
+mozilla::layers::BufferRecycleBin::BufferRecycleBin() :
+ mLock("mozilla.layers.BufferRecycleBin.mLock") {
+}
+
+void mozilla::layers::BufferRecycleBin::RecycleBuffer(uint8_t* buffer, uint32_t size) {
+ PR_Free(buffer);
+}
+
+uint8_t *mozilla::layers::BufferRecycleBin::GetBuffer(uint32_t size) {
+ return (uint8_t *)PR_MALLOC(size);
+}
+
+// YCbCrImage constructor (from ImageLayers.cpp)
+mozilla::layers::PlanarYCbCrImage::PlanarYCbCrImage(BufferRecycleBin *aRecycleBin)
+ : Image(nsnull, ImageFormat::PLANAR_YCBCR)
+ , mBufferSize(0)
+ , mRecycleBin(aRecycleBin)
+{
+}
+
+
+#endif
+#endif
+
+
+#endif
diff --git a/media/webrtc/signaling/test/FakePCObserver.h b/media/webrtc/signaling/test/FakePCObserver.h
new file mode 100644
index 000000000..460059b7f
--- /dev/null
+++ b/media/webrtc/signaling/test/FakePCObserver.h
@@ -0,0 +1,112 @@
+/* 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/. */
+
+#ifndef TEST_PCOBSERVER_H_
+#define TEST_PCOBSERVER_H_
+
+#include "nsNetCID.h"
+#include "nsITimer.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIComponentManager.h"
+#include "nsIComponentRegistrar.h"
+
+#include "mozilla/Mutex.h"
+#include "AudioSegment.h"
+#include "MediaSegment.h"
+#include "StreamTracks.h"
+#include "nsTArray.h"
+#include "nsIRunnable.h"
+#include "nsISupportsImpl.h"
+#include "mozilla/dom/PeerConnectionObserverEnumsBinding.h"
+#include "PeerConnectionImpl.h"
+#include "nsWeakReference.h"
+
+namespace mozilla {
+class PeerConnectionImpl;
+}
+
+class nsIDOMWindow;
+class nsIDOMDataChannel;
+
+namespace test {
+
+class AFakePCObserver : public nsSupportsWeakReference
+{
+protected:
+ typedef mozilla::ErrorResult ER;
+public:
+ enum Action {
+ OFFER,
+ ANSWER
+ };
+
+ enum ResponseState {
+ stateNoResponse,
+ stateSuccess,
+ stateError
+ };
+
+ AFakePCObserver(mozilla::PeerConnectionImpl *peerConnection,
+ const std::string &aName) :
+ state(stateNoResponse), addIceSuccessCount(0),
+ onAddStreamCalled(false),
+ name(aName),
+ pc(peerConnection) {
+ }
+
+ AFakePCObserver() :
+ state(stateNoResponse), addIceSuccessCount(0),
+ onAddStreamCalled(false),
+ name(""),
+ pc(nullptr) {
+ }
+
+ virtual ~AFakePCObserver() {}
+
+ std::vector<mozilla::DOMMediaStream *> GetStreams() { return streams; }
+
+ ResponseState state;
+ std::string lastString;
+ mozilla::PeerConnectionImpl::Error lastStatusCode;
+ mozilla::dom::PCObserverStateType lastStateType;
+ int addIceSuccessCount;
+ bool onAddStreamCalled;
+ std::string name;
+ std::vector<std::string> candidates;
+
+ NS_IMETHOD OnCreateOfferSuccess(const char* offer, ER&) = 0;
+ NS_IMETHOD OnCreateOfferError(uint32_t code, const char *msg, ER&) = 0;
+ NS_IMETHOD OnCreateAnswerSuccess(const char* answer, ER&) = 0;
+ NS_IMETHOD OnCreateAnswerError(uint32_t code, const char *msg, ER&) = 0;
+ NS_IMETHOD OnSetLocalDescriptionSuccess(ER&) = 0;
+ NS_IMETHOD OnSetRemoteDescriptionSuccess(ER&) = 0;
+ NS_IMETHOD OnSetLocalDescriptionError(uint32_t code, const char *msg, ER&) = 0;
+ NS_IMETHOD OnSetRemoteDescriptionError(uint32_t code, const char *msg, ER&) = 0;
+ NS_IMETHOD NotifyDataChannel(nsIDOMDataChannel *channel, ER&) = 0;
+ NS_IMETHOD OnStateChange(mozilla::dom::PCObserverStateType state_type, ER&,
+ void* = nullptr) = 0;
+ NS_IMETHOD OnAddStream(mozilla::DOMMediaStream &stream, ER&) = 0;
+ NS_IMETHOD OnRemoveStream(mozilla::DOMMediaStream &stream, ER&) = 0;
+ NS_IMETHOD OnAddTrack(mozilla::dom::MediaStreamTrack &track, ER&) = 0;
+ NS_IMETHOD OnRemoveTrack(mozilla::dom::MediaStreamTrack &track, ER&) = 0;
+ NS_IMETHOD OnReplaceTrackSuccess(ER&) = 0;
+ NS_IMETHOD OnReplaceTrackError(uint32_t code, const char *msg, ER&) = 0;
+ NS_IMETHOD OnAddIceCandidateSuccess(ER&) = 0;
+ NS_IMETHOD OnAddIceCandidateError(uint32_t code, const char *msg, ER&) = 0;
+ NS_IMETHOD OnIceCandidate(uint16_t level, const char *mid,
+ const char *candidate, ER&) = 0;
+ NS_IMETHOD OnNegotiationNeeded(ER&) = 0;
+protected:
+ mozilla::PeerConnectionImpl *pc;
+ std::vector<mozilla::DOMMediaStream *> streams;
+};
+}
+
+namespace mozilla {
+namespace dom {
+typedef test::AFakePCObserver PeerConnectionObserver;
+}
+}
+
+#endif
diff --git a/media/webrtc/signaling/test/common.build b/media/webrtc/signaling/test/common.build
new file mode 100644
index 000000000..3e5450f5d
--- /dev/null
+++ b/media/webrtc/signaling/test/common.build
@@ -0,0 +1,134 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+if CONFIG['OS_TARGET'] in ('Darwin', 'Android'):
+ DEFINES['GTEST_USE_OWN_TR1_TUPLE'] = 1
+
+for var in ('MOZILLA_EXTERNAL_LINKAGE', 'USE_FAKE_MEDIA_STREAMS', 'USE_FAKE_PCOBSERVER',
+ 'NR_SOCKET_IS_VOID_PTR', 'HAVE_STRDUP'):
+ DEFINES[var] = True
+
+LOCAL_INCLUDES += [
+ '!/dist/include/mozilla/dom', # Binding headers (because binding
+ # implementations include them).
+ '!/dom/bindings', # Binding implementations (urk).
+ '/dom/media/',
+ '/ipc/chromium/src',
+ '/media/mtransport',
+ '/media/mtransport/test',
+ '/media/mtransport/third_party/nICEr/src/ice',
+ '/media/mtransport/third_party/nICEr/src/net',
+ '/media/mtransport/third_party/nICEr/src/stun',
+ '/media/mtransport/third_party/nrappkit/src/event',
+ '/media/mtransport/third_party/nrappkit/src/log',
+ '/media/mtransport/third_party/nrappkit/src/plugin',
+ '/media/mtransport/third_party/nrappkit/src/port/generic/include',
+ '/media/mtransport/third_party/nrappkit/src/registry',
+ '/media/mtransport/third_party/nrappkit/src/share',
+ '/media/mtransport/third_party/nrappkit/src/stats',
+ '/media/mtransport/third_party/nrappkit/src/util/libekr',
+ '/media/webrtc',
+ '/media/webrtc/signaling/src/common/browser_logging',
+ '/media/webrtc/signaling/src/common/time_profiling',
+ '/media/webrtc/signaling/src/media',
+ '/media/webrtc/signaling/src/media-conduit',
+ '/media/webrtc/signaling/src/mediapipeline',
+ '/media/webrtc/signaling/src/peerconnection',
+ '/media/webrtc/signaling/src/sdp/sipcc',
+ '/media/webrtc/trunk',
+ '/media/webrtc/trunk/testing/gtest/include',
+ '/xpcom/base',
+]
+
+if CONFIG['OS_TARGET'] == 'Android':
+ LOCAL_INCLUDES += [
+ '/media/mtransport/third_party/nrappkit/src/port/android/include',
+ ]
+
+if CONFIG['OS_TARGET'] == 'Linux':
+ LOCAL_INCLUDES += [
+ '/media/mtransport/third_party/nrappkit/src/port/linux/include',
+ ]
+
+if CONFIG['OS_TARGET'] == 'Darwin':
+ LOCAL_INCLUDES += [
+ '/media/mtransport/third_party/nrappkit/src/port/darwin/include',
+ ]
+ OS_LIBS += [
+ '-framework AudioToolbox',
+ '-framework AudioUnit',
+ '-framework Carbon',
+ '-framework CoreAudio',
+ '-framework OpenGL',
+ '-framework AVFoundation',
+ '-framework CoreMedia',
+ '-framework QuartzCore',
+ '-framework Security',
+ '-framework SystemConfiguration',
+ '-framework IOKit',
+ '-F%s' % CONFIG['MACOS_PRIVATE_FRAMEWORKS_DIR'],
+ '-framework CoreUI',
+ ]
+
+if CONFIG['OS_TARGET'] in ('DragonFly', 'FreeBSD', 'NetBSD', 'OpenBSD'):
+ LOCAL_INCLUDES += [
+ '/media/mtransport/third_party/nrappkit/src/port/darwin/include',
+ ]
+
+USE_LIBS += [
+ '/media/webrtc/trunk/testing/gtest_gtest/gtest',
+ 'chromium_atomics',
+ 'gkmedias',
+ 'nksrtp_s',
+ 'nss',
+ 'webrtc',
+ 'yuv',
+ 'zlib',
+]
+
+if CONFIG['JS_SHARED_LIBRARY']:
+ USE_LIBS += [
+ 'js',
+ ]
+
+USE_LIBS += ['mozglue']
+
+OS_LIBS += CONFIG['MOZ_WEBRTC_X11_LIBS']
+OS_LIBS += CONFIG['REALTIME_LIBS']
+
+if CONFIG['MOZ_ALSA']:
+ OS_LIBS += CONFIG['MOZ_ALSA_LIBS']
+
+if CONFIG['MOZ_SYSTEM_JPEG']:
+ OS_LIBS += CONFIG['MOZ_JPEG_LIBS']
+
+if CONFIG['MOZ_SYSTEM_LIBVPX']:
+ OS_LIBS += CONFIG['MOZ_LIBVPX_LIBS']
+
+if not CONFIG['MOZ_TREE_PIXMAN']:
+ OS_LIBS += CONFIG['MOZ_PIXMAN_LIBS']
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gtk2':
+ OS_LIBS += CONFIG['XLIBS']
+ OS_LIBS += CONFIG['MOZ_GTK2_LIBS']
+ OS_LIBS += [
+ 'gmodule-2.0',
+ 'gthread-2.0',
+ ]
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gtk3':
+ OS_LIBS += CONFIG['XLIBS']
+ OS_LIBS += CONFIG['MOZ_GTK3_LIBS']
+ USE_LIBS += [
+ 'freetype',
+ ]
+
+if CONFIG['OS_TARGET'] in ('Linux', 'DragonFly', 'FreeBSD', 'NetBSD',
+ 'OpenBSD'):
+ OS_LIBS += CONFIG['MOZ_CAIRO_OSLIBS']
+
+if CONFIG['OS_TARGET'] == 'Darwin':
+ OS_LIBS += CONFIG['TK_LIBS']
diff --git a/media/webrtc/signaling/test/jsep_session_unittest.cpp b/media/webrtc/signaling/test/jsep_session_unittest.cpp
new file mode 100644
index 000000000..d29400771
--- /dev/null
+++ b/media/webrtc/signaling/test/jsep_session_unittest.cpp
@@ -0,0 +1,4235 @@
+/* -*- 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 "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();
+}
diff --git a/media/webrtc/signaling/test/jsep_track_unittest.cpp b/media/webrtc/signaling/test/jsep_track_unittest.cpp
new file mode 100644
index 000000000..a09d47276
--- /dev/null
+++ b/media/webrtc/signaling/test/jsep_track_unittest.cpp
@@ -0,0 +1,1269 @@
+/* -*- 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/. */
+
+#define GTEST_HAS_RTTI 0
+#include "gtest/gtest.h"
+#include "gtest_utils.h"
+
+// Magic linker includes :(
+#include "FakeMediaStreams.h"
+#include "FakeMediaStreamsImpl.h"
+#include "FakeLogging.h"
+
+#include "signaling/src/jsep/JsepTrack.h"
+#include "signaling/src/sdp/SipccSdp.h"
+#include "signaling/src/sdp/SdpHelper.h"
+
+#include "mtransport_test_utils.h"
+
+#include "FakeIPC.h"
+#include "FakeIPC.cpp"
+
+#include "TestHarness.h"
+
+namespace mozilla {
+
+class JsepTrackTest : public ::testing::Test
+{
+ public:
+ JsepTrackTest() {}
+
+ std::vector<JsepCodecDescription*>
+ MakeCodecs(bool addFecCodecs = false,
+ bool preferRed = false,
+ bool addDtmfCodec = false) const
+ {
+ std::vector<JsepCodecDescription*> results;
+ results.push_back(
+ new JsepAudioCodecDescription("1", "opus", 48000, 2, 960, 40000));
+ results.push_back(
+ new JsepAudioCodecDescription("9", "G722", 8000, 1, 320, 64000));
+ if (addDtmfCodec) {
+ results.push_back(
+ new JsepAudioCodecDescription("101", "telephone-event",
+ 8000, 1, 0, 0));
+ }
+
+ JsepVideoCodecDescription* red = nullptr;
+ if (addFecCodecs && preferRed) {
+ red = new JsepVideoCodecDescription(
+ "122",
+ "red",
+ 90000
+ );
+ results.push_back(red);
+ }
+
+ JsepVideoCodecDescription* vp8 =
+ new JsepVideoCodecDescription("120", "VP8", 90000);
+ vp8->mConstraints.maxFs = 12288;
+ vp8->mConstraints.maxFps = 60;
+ results.push_back(vp8);
+
+ JsepVideoCodecDescription* h264 =
+ new JsepVideoCodecDescription("126", "H264", 90000);
+ h264->mPacketizationMode = 1;
+ h264->mProfileLevelId = 0x42E00D;
+ results.push_back(h264);
+
+ if (addFecCodecs) {
+ if (!preferRed) {
+ red = new JsepVideoCodecDescription(
+ "122",
+ "red",
+ 90000
+ );
+ results.push_back(red);
+ }
+ JsepVideoCodecDescription* ulpfec = new JsepVideoCodecDescription(
+ "123",
+ "ulpfec",
+ 90000
+ );
+ results.push_back(ulpfec);
+ }
+
+ results.push_back(
+ new JsepApplicationCodecDescription(
+ "5000",
+ "webrtc-datachannel",
+ 16
+ ));
+
+ // if we're doing something with red, it needs
+ // to update the redundant encodings list
+ if (red) {
+ red->UpdateRedundantEncodings(results);
+ }
+
+ return results;
+ }
+
+ void Init(SdpMediaSection::MediaType type) {
+ InitCodecs();
+ InitTracks(type);
+ InitSdp(type);
+ }
+
+ void InitCodecs() {
+ mOffCodecs.values = MakeCodecs();
+ mAnsCodecs.values = MakeCodecs();
+ }
+
+ void InitTracks(SdpMediaSection::MediaType type)
+ {
+ mSendOff = new JsepTrack(type, "stream_id", "track_id", sdp::kSend);
+ mRecvOff = new JsepTrack(type, "stream_id", "track_id", sdp::kRecv);
+ mSendOff->PopulateCodecs(mOffCodecs.values);
+ mRecvOff->PopulateCodecs(mOffCodecs.values);
+
+ mSendAns = new JsepTrack(type, "stream_id", "track_id", sdp::kSend);
+ mRecvAns = new JsepTrack(type, "stream_id", "track_id", sdp::kRecv);
+ mSendAns->PopulateCodecs(mAnsCodecs.values);
+ mRecvAns->PopulateCodecs(mAnsCodecs.values);
+ }
+
+ void InitSdp(SdpMediaSection::MediaType type)
+ {
+ mOffer.reset(new SipccSdp(SdpOrigin("", 0, 0, sdp::kIPv4, "")));
+ mOffer->AddMediaSection(
+ type,
+ SdpDirectionAttribute::kInactive,
+ 0,
+ SdpHelper::GetProtocolForMediaType(type),
+ sdp::kIPv4,
+ "0.0.0.0");
+ mAnswer.reset(new SipccSdp(SdpOrigin("", 0, 0, sdp::kIPv4, "")));
+ mAnswer->AddMediaSection(
+ type,
+ SdpDirectionAttribute::kInactive,
+ 0,
+ SdpHelper::GetProtocolForMediaType(type),
+ sdp::kIPv4,
+ "0.0.0.0");
+ }
+
+ SdpMediaSection& GetOffer()
+ {
+ return mOffer->GetMediaSection(0);
+ }
+
+ SdpMediaSection& GetAnswer()
+ {
+ return mAnswer->GetMediaSection(0);
+ }
+
+ void CreateOffer()
+ {
+ if (mSendOff) {
+ mSendOff->AddToOffer(&GetOffer());
+ }
+
+ if (mRecvOff) {
+ mRecvOff->AddToOffer(&GetOffer());
+ }
+ }
+
+ void CreateAnswer()
+ {
+ if (mSendAns && GetOffer().IsReceiving()) {
+ mSendAns->AddToAnswer(GetOffer(), &GetAnswer());
+ }
+
+ if (mRecvAns && GetOffer().IsSending()) {
+ mRecvAns->AddToAnswer(GetOffer(), &GetAnswer());
+ }
+ }
+
+ void Negotiate()
+ {
+ std::cerr << "Offer SDP: " << std::endl;
+ mOffer->Serialize(std::cerr);
+
+ std::cerr << "Answer SDP: " << std::endl;
+ mAnswer->Serialize(std::cerr);
+
+ if (mSendAns && GetAnswer().IsSending()) {
+ mSendAns->Negotiate(GetAnswer(), GetOffer());
+ }
+
+ if (mRecvAns && GetAnswer().IsReceiving()) {
+ mRecvAns->Negotiate(GetAnswer(), GetOffer());
+ }
+
+ if (mSendOff && GetAnswer().IsReceiving()) {
+ mSendOff->Negotiate(GetAnswer(), GetAnswer());
+ }
+
+ if (mRecvOff && GetAnswer().IsSending()) {
+ mRecvOff->Negotiate(GetAnswer(), GetAnswer());
+ }
+ }
+
+ void OfferAnswer()
+ {
+ CreateOffer();
+ CreateAnswer();
+ Negotiate();
+ SanityCheck();
+ }
+
+ static size_t EncodingCount(const RefPtr<JsepTrack>& track)
+ {
+ return track->GetNegotiatedDetails()->GetEncodingCount();
+ }
+
+ // TODO: Look into writing a macro that wraps an ASSERT_ and returns false
+ // if it fails (probably requires writing a bool-returning function that
+ // takes a void-returning lambda with a bool outparam, which will in turn
+ // invokes the ASSERT_)
+ static void CheckEncodingCount(size_t expected,
+ const RefPtr<JsepTrack>& send,
+ const RefPtr<JsepTrack>& recv)
+ {
+ if (expected) {
+ ASSERT_TRUE(!!send);
+ ASSERT_TRUE(send->GetNegotiatedDetails());
+ ASSERT_TRUE(!!recv);
+ ASSERT_TRUE(recv->GetNegotiatedDetails());
+ }
+
+ if (send && send->GetNegotiatedDetails()) {
+ ASSERT_EQ(expected, send->GetNegotiatedDetails()->GetEncodingCount());
+ }
+
+ if (recv && recv->GetNegotiatedDetails()) {
+ ASSERT_EQ(expected, recv->GetNegotiatedDetails()->GetEncodingCount());
+ }
+ }
+
+ void CheckOffEncodingCount(size_t expected) const
+ {
+ CheckEncodingCount(expected, mSendOff, mRecvAns);
+ }
+
+ void CheckAnsEncodingCount(size_t expected) const
+ {
+ CheckEncodingCount(expected, mSendAns, mRecvOff);
+ }
+
+ const JsepCodecDescription*
+ GetCodec(const JsepTrack& track,
+ SdpMediaSection::MediaType type,
+ size_t expectedSize,
+ size_t codecIndex) const
+ {
+ if (!track.GetNegotiatedDetails() ||
+ track.GetNegotiatedDetails()->GetEncodingCount() != 1U ||
+ track.GetMediaType() != type) {
+ return nullptr;
+ }
+ const std::vector<JsepCodecDescription*>& codecs =
+ track.GetNegotiatedDetails()->GetEncoding(0).GetCodecs();
+ // it should not be possible for codecs to have a different type
+ // than the track, but we'll check the codec here just in case.
+ if (codecs.size() != expectedSize || codecIndex >= expectedSize ||
+ codecs[codecIndex]->mType != type) {
+ return nullptr;
+ }
+ return codecs[codecIndex];
+ }
+
+ const JsepVideoCodecDescription*
+ GetVideoCodec(const JsepTrack& track,
+ size_t expectedSize = 1,
+ size_t codecIndex = 0) const
+ {
+ return static_cast<const JsepVideoCodecDescription*>
+ (GetCodec(track, SdpMediaSection::kVideo, expectedSize, codecIndex));
+ }
+
+ const JsepAudioCodecDescription*
+ GetAudioCodec(const JsepTrack& track,
+ size_t expectedSize = 1,
+ size_t codecIndex = 0) const
+ {
+ return static_cast<const JsepAudioCodecDescription*>
+ (GetCodec(track, SdpMediaSection::kAudio, expectedSize, codecIndex));
+ }
+
+ void CheckOtherFbsSize(const JsepTrack& track, size_t expected) const
+ {
+ const JsepVideoCodecDescription* videoCodec = GetVideoCodec(track);
+ ASSERT_NE(videoCodec, nullptr);
+ ASSERT_EQ(videoCodec->mOtherFbTypes.size(), expected);
+ }
+
+ void CheckOtherFbExists(const JsepTrack& track,
+ SdpRtcpFbAttributeList::Type type) const
+ {
+ const JsepVideoCodecDescription* videoCodec = GetVideoCodec(track);
+ ASSERT_NE(videoCodec, nullptr);
+ for (const auto& fb : videoCodec->mOtherFbTypes) {
+ if (fb.type == type) {
+ return; // found the RtcpFb type, so stop looking
+ }
+ }
+ FAIL(); // RtcpFb type not found
+ }
+
+ void SanityCheckRtcpFbs(const JsepVideoCodecDescription& a,
+ const JsepVideoCodecDescription& b) const
+ {
+ ASSERT_EQ(a.mNackFbTypes.size(), b.mNackFbTypes.size());
+ ASSERT_EQ(a.mAckFbTypes.size(), b.mAckFbTypes.size());
+ ASSERT_EQ(a.mCcmFbTypes.size(), b.mCcmFbTypes.size());
+ ASSERT_EQ(a.mOtherFbTypes.size(), b.mOtherFbTypes.size());
+ }
+
+ void SanityCheckCodecs(const JsepCodecDescription& a,
+ const JsepCodecDescription& b) const
+ {
+ ASSERT_EQ(a.mType, b.mType);
+ ASSERT_EQ(a.mDefaultPt, b.mDefaultPt);
+ ASSERT_EQ(a.mName, b.mName);
+ ASSERT_EQ(a.mClock, b.mClock);
+ ASSERT_EQ(a.mChannels, b.mChannels);
+ ASSERT_NE(a.mDirection, b.mDirection);
+ // These constraints are for fmtp and rid, which _are_ signaled
+ ASSERT_EQ(a.mConstraints, b.mConstraints);
+
+ if (a.mType == SdpMediaSection::kVideo) {
+ SanityCheckRtcpFbs(static_cast<const JsepVideoCodecDescription&>(a),
+ static_cast<const JsepVideoCodecDescription&>(b));
+ }
+ }
+
+ void SanityCheckEncodings(const JsepTrackEncoding& a,
+ const JsepTrackEncoding& b) const
+ {
+ ASSERT_EQ(a.GetCodecs().size(), b.GetCodecs().size());
+ for (size_t i = 0; i < a.GetCodecs().size(); ++i) {
+ SanityCheckCodecs(*a.GetCodecs()[i], *b.GetCodecs()[i]);
+ }
+
+ ASSERT_EQ(a.mRid, b.mRid);
+ // mConstraints will probably differ, since they are not signaled to the
+ // other side.
+ }
+
+ void SanityCheckNegotiatedDetails(const JsepTrackNegotiatedDetails& a,
+ const JsepTrackNegotiatedDetails& b) const
+ {
+ ASSERT_EQ(a.GetEncodingCount(), b.GetEncodingCount());
+ for (size_t i = 0; i < a.GetEncodingCount(); ++i) {
+ SanityCheckEncodings(a.GetEncoding(i), b.GetEncoding(i));
+ }
+
+ ASSERT_EQ(a.GetUniquePayloadTypes().size(),
+ b.GetUniquePayloadTypes().size());
+ for (size_t i = 0; i < a.GetUniquePayloadTypes().size(); ++i) {
+ ASSERT_EQ(a.GetUniquePayloadTypes()[i], b.GetUniquePayloadTypes()[i]);
+ }
+ }
+
+ void SanityCheckTracks(const JsepTrack& a, const JsepTrack& b) const
+ {
+ if (!a.GetNegotiatedDetails()) {
+ ASSERT_FALSE(!!b.GetNegotiatedDetails());
+ return;
+ }
+
+ ASSERT_TRUE(!!a.GetNegotiatedDetails());
+ ASSERT_TRUE(!!b.GetNegotiatedDetails());
+ ASSERT_EQ(a.GetMediaType(), b.GetMediaType());
+ ASSERT_EQ(a.GetStreamId(), b.GetStreamId());
+ ASSERT_EQ(a.GetTrackId(), b.GetTrackId());
+ ASSERT_EQ(a.GetCNAME(), b.GetCNAME());
+ ASSERT_NE(a.GetDirection(), b.GetDirection());
+ ASSERT_EQ(a.GetSsrcs().size(), b.GetSsrcs().size());
+ for (size_t i = 0; i < a.GetSsrcs().size(); ++i) {
+ ASSERT_EQ(a.GetSsrcs()[i], b.GetSsrcs()[i]);
+ }
+
+ SanityCheckNegotiatedDetails(*a.GetNegotiatedDetails(),
+ *b.GetNegotiatedDetails());
+ }
+
+ void SanityCheck() const
+ {
+ if (mSendOff && mRecvAns) {
+ SanityCheckTracks(*mSendOff, *mRecvAns);
+ }
+ if (mRecvOff && mSendAns) {
+ SanityCheckTracks(*mRecvOff, *mSendAns);
+ }
+ }
+
+ protected:
+ RefPtr<JsepTrack> mSendOff;
+ RefPtr<JsepTrack> mRecvOff;
+ RefPtr<JsepTrack> mSendAns;
+ RefPtr<JsepTrack> mRecvAns;
+ PtrVector<JsepCodecDescription> mOffCodecs;
+ PtrVector<JsepCodecDescription> mAnsCodecs;
+ UniquePtr<Sdp> mOffer;
+ UniquePtr<Sdp> mAnswer;
+};
+
+TEST_F(JsepTrackTest, CreateDestroy)
+{
+ Init(SdpMediaSection::kAudio);
+}
+
+TEST_F(JsepTrackTest, AudioNegotiation)
+{
+ Init(SdpMediaSection::kAudio);
+ OfferAnswer();
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(1);
+}
+
+TEST_F(JsepTrackTest, VideoNegotiation)
+{
+ Init(SdpMediaSection::kVideo);
+ OfferAnswer();
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(1);
+}
+
+class CheckForCodecType
+{
+public:
+ explicit CheckForCodecType(SdpMediaSection::MediaType type,
+ bool *result) :
+ mResult(result),
+ mType(type) {}
+
+ void operator()(JsepCodecDescription* codec) {
+ if (codec->mType == mType) {
+ *mResult = true;
+ }
+ }
+
+private:
+ bool *mResult;
+ SdpMediaSection::MediaType mType;
+};
+
+TEST_F(JsepTrackTest, CheckForMismatchedAudioCodecAndVideoTrack)
+{
+ PtrVector<JsepCodecDescription> offerCodecs;
+
+ // make codecs including telephone-event (an audio codec)
+ offerCodecs.values = MakeCodecs(false, false, true);
+ RefPtr<JsepTrack> videoTrack = new JsepTrack(SdpMediaSection::kVideo,
+ "stream_id",
+ "track_id",
+ sdp::kSend);
+ // populate codecs and then make sure we don't have any audio codecs
+ // in the video track
+ videoTrack->PopulateCodecs(offerCodecs.values);
+
+ bool found = false;
+ videoTrack->ForEachCodec(CheckForCodecType(SdpMediaSection::kAudio, &found));
+ ASSERT_FALSE(found);
+
+ found = false;
+ videoTrack->ForEachCodec(CheckForCodecType(SdpMediaSection::kVideo, &found));
+ ASSERT_TRUE(found); // for sanity, make sure we did find video codecs
+}
+
+TEST_F(JsepTrackTest, CheckVideoTrackWithHackedDtmfSdp)
+{
+ Init(SdpMediaSection::kVideo);
+ CreateOffer();
+ // make sure we don't find sdp containing telephone-event in video track
+ ASSERT_EQ(mOffer->ToString().find("a=rtpmap:101 telephone-event"),
+ std::string::npos);
+ // force audio codec telephone-event into video m= section of offer
+ GetOffer().AddCodec("101", "telephone-event", 8000, 1);
+ // make sure we _do_ find sdp containing telephone-event in video track
+ ASSERT_NE(mOffer->ToString().find("a=rtpmap:101 telephone-event"),
+ std::string::npos);
+
+ CreateAnswer();
+ // make sure we don't find sdp containing telephone-event in video track
+ ASSERT_EQ(mAnswer->ToString().find("a=rtpmap:101 telephone-event"),
+ std::string::npos);
+ // force audio codec telephone-event into video m= section of answer
+ GetAnswer().AddCodec("101", "telephone-event", 8000, 1);
+ // make sure we _do_ find sdp containing telephone-event in video track
+ ASSERT_NE(mAnswer->ToString().find("a=rtpmap:101 telephone-event"),
+ std::string::npos);
+
+ Negotiate();
+ SanityCheck();
+
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(1);
+
+ ASSERT_TRUE(mSendOff.get());
+ ASSERT_TRUE(mRecvOff.get());
+ ASSERT_TRUE(mSendAns.get());
+ ASSERT_TRUE(mRecvAns.get());
+
+ // make sure we still don't find any audio codecs in the video track after
+ // hacking the sdp
+ bool found = false;
+ mSendOff->ForEachCodec(CheckForCodecType(SdpMediaSection::kAudio, &found));
+ ASSERT_FALSE(found);
+ mRecvOff->ForEachCodec(CheckForCodecType(SdpMediaSection::kAudio, &found));
+ ASSERT_FALSE(found);
+ mSendAns->ForEachCodec(CheckForCodecType(SdpMediaSection::kAudio, &found));
+ ASSERT_FALSE(found);
+ mRecvAns->ForEachCodec(CheckForCodecType(SdpMediaSection::kAudio, &found));
+ ASSERT_FALSE(found);
+}
+
+TEST_F(JsepTrackTest, AudioNegotiationOffererDtmf)
+{
+ mOffCodecs.values = MakeCodecs(false, false, true);
+ mAnsCodecs.values = MakeCodecs(false, false, false);
+
+ InitTracks(SdpMediaSection::kAudio);
+ InitSdp(SdpMediaSection::kAudio);
+ OfferAnswer();
+
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(1);
+
+ ASSERT_NE(mOffer->ToString().find("a=rtpmap:101 telephone-event"),
+ std::string::npos);
+ ASSERT_EQ(mAnswer->ToString().find("a=rtpmap:101 telephone-event"),
+ std::string::npos);
+
+ ASSERT_NE(mOffer->ToString().find("a=fmtp:101 0-15"), std::string::npos);
+ ASSERT_EQ(mAnswer->ToString().find("a=fmtp:101"), std::string::npos);
+
+ const JsepAudioCodecDescription* track = nullptr;
+ ASSERT_TRUE((track = GetAudioCodec(*mSendOff)));
+ ASSERT_EQ("1", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(*mRecvOff)));
+ ASSERT_EQ("1", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(*mSendAns)));
+ ASSERT_EQ("1", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(*mRecvAns)));
+ ASSERT_EQ("1", track->mDefaultPt);
+}
+
+TEST_F(JsepTrackTest, AudioNegotiationAnswererDtmf)
+{
+ mOffCodecs.values = MakeCodecs(false, false, false);
+ mAnsCodecs.values = MakeCodecs(false, false, true);
+
+ InitTracks(SdpMediaSection::kAudio);
+ InitSdp(SdpMediaSection::kAudio);
+ OfferAnswer();
+
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(1);
+
+ ASSERT_EQ(mOffer->ToString().find("a=rtpmap:101 telephone-event"),
+ std::string::npos);
+ ASSERT_EQ(mAnswer->ToString().find("a=rtpmap:101 telephone-event"),
+ std::string::npos);
+
+ ASSERT_EQ(mOffer->ToString().find("a=fmtp:101 0-15"), std::string::npos);
+ ASSERT_EQ(mAnswer->ToString().find("a=fmtp:101"), std::string::npos);
+
+ const JsepAudioCodecDescription* track = nullptr;
+ ASSERT_TRUE((track = GetAudioCodec(*mSendOff)));
+ ASSERT_EQ("1", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(*mRecvOff)));
+ ASSERT_EQ("1", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(*mSendAns)));
+ ASSERT_EQ("1", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(*mRecvAns)));
+ ASSERT_EQ("1", track->mDefaultPt);
+}
+
+TEST_F(JsepTrackTest, AudioNegotiationOffererAnswererDtmf)
+{
+ mOffCodecs.values = MakeCodecs(false, false, true);
+ mAnsCodecs.values = MakeCodecs(false, false, true);
+
+ InitTracks(SdpMediaSection::kAudio);
+ InitSdp(SdpMediaSection::kAudio);
+ OfferAnswer();
+
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(1);
+
+ ASSERT_NE(mOffer->ToString().find("a=rtpmap:101 telephone-event"),
+ std::string::npos);
+ ASSERT_NE(mAnswer->ToString().find("a=rtpmap:101 telephone-event"),
+ std::string::npos);
+
+ ASSERT_NE(mOffer->ToString().find("a=fmtp:101 0-15"), std::string::npos);
+ ASSERT_NE(mAnswer->ToString().find("a=fmtp:101 0-15"), std::string::npos);
+
+ const JsepAudioCodecDescription* track = nullptr;
+ ASSERT_TRUE((track = GetAudioCodec(*mSendOff, 2)));
+ ASSERT_EQ("1", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(*mRecvOff, 2)));
+ ASSERT_EQ("1", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(*mSendAns, 2)));
+ ASSERT_EQ("1", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(*mRecvAns, 2)));
+ ASSERT_EQ("1", track->mDefaultPt);
+
+ ASSERT_TRUE((track = GetAudioCodec(*mSendOff, 2, 1)));
+ ASSERT_EQ("101", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(*mRecvOff, 2, 1)));
+ ASSERT_EQ("101", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(*mSendAns, 2, 1)));
+ ASSERT_EQ("101", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(*mRecvAns, 2, 1)));
+ ASSERT_EQ("101", track->mDefaultPt);
+}
+
+TEST_F(JsepTrackTest, AudioNegotiationDtmfOffererNoFmtpAnswererFmtp)
+{
+ mOffCodecs.values = MakeCodecs(false, false, true);
+ mAnsCodecs.values = MakeCodecs(false, false, true);
+
+ InitTracks(SdpMediaSection::kAudio);
+ InitSdp(SdpMediaSection::kAudio);
+
+ CreateOffer();
+ GetOffer().RemoveFmtp("101");
+
+ CreateAnswer();
+
+ Negotiate();
+ SanityCheck();
+
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(1);
+
+ ASSERT_NE(mOffer->ToString().find("a=rtpmap:101 telephone-event"),
+ std::string::npos);
+ ASSERT_NE(mAnswer->ToString().find("a=rtpmap:101 telephone-event"),
+ std::string::npos);
+
+ ASSERT_EQ(mOffer->ToString().find("a=fmtp:101"), std::string::npos);
+ ASSERT_NE(mAnswer->ToString().find("a=fmtp:101 0-15"), std::string::npos);
+
+ const JsepAudioCodecDescription* track = nullptr;
+ ASSERT_TRUE((track = GetAudioCodec(*mSendOff, 2)));
+ ASSERT_EQ("1", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(*mRecvOff, 2)));
+ ASSERT_EQ("1", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(*mSendAns, 2)));
+ ASSERT_EQ("1", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(*mRecvAns, 2)));
+ ASSERT_EQ("1", track->mDefaultPt);
+
+ ASSERT_TRUE((track = GetAudioCodec(*mSendOff, 2, 1)));
+ ASSERT_EQ("101", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(*mRecvOff, 2, 1)));
+ ASSERT_EQ("101", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(*mSendAns, 2, 1)));
+ ASSERT_EQ("101", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(*mRecvAns, 2, 1)));
+ ASSERT_EQ("101", track->mDefaultPt);
+}
+
+TEST_F(JsepTrackTest, AudioNegotiationDtmfOffererFmtpAnswererNoFmtp)
+{
+ mOffCodecs.values = MakeCodecs(false, false, true);
+ mAnsCodecs.values = MakeCodecs(false, false, true);
+
+ InitTracks(SdpMediaSection::kAudio);
+ InitSdp(SdpMediaSection::kAudio);
+
+ CreateOffer();
+
+ CreateAnswer();
+ GetAnswer().RemoveFmtp("101");
+
+ Negotiate();
+ SanityCheck();
+
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(1);
+
+ ASSERT_NE(mOffer->ToString().find("a=rtpmap:101 telephone-event"),
+ std::string::npos);
+ ASSERT_NE(mAnswer->ToString().find("a=rtpmap:101 telephone-event"),
+ std::string::npos);
+
+ ASSERT_NE(mOffer->ToString().find("a=fmtp:101 0-15"), std::string::npos);
+ ASSERT_EQ(mAnswer->ToString().find("a=fmtp:101"), std::string::npos);
+
+ const JsepAudioCodecDescription* track = nullptr;
+ ASSERT_TRUE((track = GetAudioCodec(*mSendOff, 2)));
+ ASSERT_EQ("1", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(*mRecvOff, 2)));
+ ASSERT_EQ("1", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(*mSendAns, 2)));
+ ASSERT_EQ("1", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(*mRecvAns, 2)));
+ ASSERT_EQ("1", track->mDefaultPt);
+
+ ASSERT_TRUE((track = GetAudioCodec(*mSendOff, 2, 1)));
+ ASSERT_EQ("101", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(*mRecvOff, 2, 1)));
+ ASSERT_EQ("101", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(*mSendAns, 2, 1)));
+ ASSERT_EQ("101", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(*mRecvAns, 2, 1)));
+ ASSERT_EQ("101", track->mDefaultPt);
+}
+
+TEST_F(JsepTrackTest, AudioNegotiationDtmfOffererNoFmtpAnswererNoFmtp)
+{
+ mOffCodecs.values = MakeCodecs(false, false, true);
+ mAnsCodecs.values = MakeCodecs(false, false, true);
+
+ InitTracks(SdpMediaSection::kAudio);
+ InitSdp(SdpMediaSection::kAudio);
+
+ CreateOffer();
+ GetOffer().RemoveFmtp("101");
+
+ CreateAnswer();
+ GetAnswer().RemoveFmtp("101");
+
+ Negotiate();
+ SanityCheck();
+
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(1);
+
+ ASSERT_NE(mOffer->ToString().find("a=rtpmap:101 telephone-event"),
+ std::string::npos);
+ ASSERT_NE(mAnswer->ToString().find("a=rtpmap:101 telephone-event"),
+ std::string::npos);
+
+ ASSERT_EQ(mOffer->ToString().find("a=fmtp:101"), std::string::npos);
+ ASSERT_EQ(mAnswer->ToString().find("a=fmtp:101"), std::string::npos);
+
+ const JsepAudioCodecDescription* track = nullptr;
+ ASSERT_TRUE((track = GetAudioCodec(*mSendOff, 2)));
+ ASSERT_EQ("1", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(*mRecvOff, 2)));
+ ASSERT_EQ("1", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(*mSendAns, 2)));
+ ASSERT_EQ("1", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(*mRecvAns, 2)));
+ ASSERT_EQ("1", track->mDefaultPt);
+
+ ASSERT_TRUE((track = GetAudioCodec(*mSendOff, 2, 1)));
+ ASSERT_EQ("101", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(*mRecvOff, 2, 1)));
+ ASSERT_EQ("101", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(*mSendAns, 2, 1)));
+ ASSERT_EQ("101", track->mDefaultPt);
+ ASSERT_TRUE((track = GetAudioCodec(*mRecvAns, 2, 1)));
+ ASSERT_EQ("101", track->mDefaultPt);
+}
+
+TEST_F(JsepTrackTest, VideoNegotationOffererFEC)
+{
+ mOffCodecs.values = MakeCodecs(true);
+ mAnsCodecs.values = MakeCodecs(false);
+
+ InitTracks(SdpMediaSection::kVideo);
+ InitSdp(SdpMediaSection::kVideo);
+ OfferAnswer();
+
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(1);
+
+ ASSERT_NE(mOffer->ToString().find("a=rtpmap:122 red"), std::string::npos);
+ ASSERT_NE(mOffer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
+ ASSERT_EQ(mAnswer->ToString().find("a=rtpmap:122 red"), std::string::npos);
+ ASSERT_EQ(mAnswer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
+
+ ASSERT_NE(mOffer->ToString().find("a=fmtp:122 120/126/123"), std::string::npos);
+ ASSERT_EQ(mAnswer->ToString().find("a=fmtp:122"), std::string::npos);
+
+ const JsepVideoCodecDescription* track = nullptr;
+ ASSERT_TRUE((track = GetVideoCodec(*mSendOff)));
+ ASSERT_EQ("120", track->mDefaultPt);
+ ASSERT_TRUE((track = GetVideoCodec(*mRecvOff)));
+ ASSERT_EQ("120", track->mDefaultPt);
+ ASSERT_TRUE((track = GetVideoCodec(*mSendAns)));
+ ASSERT_EQ("120", track->mDefaultPt);
+ ASSERT_TRUE((track = GetVideoCodec(*mRecvAns)));
+ ASSERT_EQ("120", track->mDefaultPt);
+}
+
+TEST_F(JsepTrackTest, VideoNegotationAnswererFEC)
+{
+ mOffCodecs.values = MakeCodecs(false);
+ mAnsCodecs.values = MakeCodecs(true);
+
+ InitTracks(SdpMediaSection::kVideo);
+ InitSdp(SdpMediaSection::kVideo);
+ OfferAnswer();
+
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(1);
+
+ ASSERT_EQ(mOffer->ToString().find("a=rtpmap:122 red"), std::string::npos);
+ ASSERT_EQ(mOffer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
+ ASSERT_EQ(mAnswer->ToString().find("a=rtpmap:122 red"), std::string::npos);
+ ASSERT_EQ(mAnswer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
+
+ ASSERT_EQ(mOffer->ToString().find("a=fmtp:122"), std::string::npos);
+ ASSERT_EQ(mAnswer->ToString().find("a=fmtp:122"), std::string::npos);
+
+ const JsepVideoCodecDescription* track = nullptr;
+ ASSERT_TRUE((track = GetVideoCodec(*mSendOff)));
+ ASSERT_EQ("120", track->mDefaultPt);
+ ASSERT_TRUE((track = GetVideoCodec(*mRecvOff)));
+ ASSERT_EQ("120", track->mDefaultPt);
+ ASSERT_TRUE((track = GetVideoCodec(*mSendAns)));
+ ASSERT_EQ("120", track->mDefaultPt);
+ ASSERT_TRUE((track = GetVideoCodec(*mRecvAns)));
+ ASSERT_EQ("120", track->mDefaultPt);
+}
+
+TEST_F(JsepTrackTest, VideoNegotationOffererAnswererFEC)
+{
+ mOffCodecs.values = MakeCodecs(true);
+ mAnsCodecs.values = MakeCodecs(true);
+
+ InitTracks(SdpMediaSection::kVideo);
+ InitSdp(SdpMediaSection::kVideo);
+ OfferAnswer();
+
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(1);
+
+ ASSERT_NE(mOffer->ToString().find("a=rtpmap:122 red"), std::string::npos);
+ ASSERT_NE(mOffer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
+ ASSERT_NE(mAnswer->ToString().find("a=rtpmap:122 red"), std::string::npos);
+ ASSERT_NE(mAnswer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
+
+ ASSERT_NE(mOffer->ToString().find("a=fmtp:122 120/126/123"), std::string::npos);
+ ASSERT_NE(mAnswer->ToString().find("a=fmtp:122 120/126/123"), std::string::npos);
+
+ const JsepVideoCodecDescription* track = nullptr;
+ ASSERT_TRUE((track = GetVideoCodec(*mSendOff, 4)));
+ ASSERT_EQ("120", track->mDefaultPt);
+ ASSERT_TRUE((track = GetVideoCodec(*mRecvOff, 4)));
+ ASSERT_EQ("120", track->mDefaultPt);
+ ASSERT_TRUE((track = GetVideoCodec(*mSendAns, 4)));
+ ASSERT_EQ("120", track->mDefaultPt);
+ ASSERT_TRUE((track = GetVideoCodec(*mRecvAns, 4)));
+ ASSERT_EQ("120", track->mDefaultPt);
+}
+
+TEST_F(JsepTrackTest, VideoNegotationOffererAnswererFECPreferred)
+{
+ mOffCodecs.values = MakeCodecs(true, true);
+ mAnsCodecs.values = MakeCodecs(true);
+
+ InitTracks(SdpMediaSection::kVideo);
+ InitSdp(SdpMediaSection::kVideo);
+ OfferAnswer();
+
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(1);
+
+ ASSERT_NE(mOffer->ToString().find("a=rtpmap:122 red"), std::string::npos);
+ ASSERT_NE(mOffer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
+ ASSERT_NE(mAnswer->ToString().find("a=rtpmap:122 red"), std::string::npos);
+ ASSERT_NE(mAnswer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
+
+ ASSERT_NE(mOffer->ToString().find("a=fmtp:122 120/126/123"), std::string::npos);
+ ASSERT_NE(mAnswer->ToString().find("a=fmtp:122 120/126/123"), std::string::npos);
+
+ const JsepVideoCodecDescription* track = nullptr;
+ ASSERT_TRUE((track = GetVideoCodec(*mSendOff, 4)));
+ ASSERT_EQ("122", track->mDefaultPt);
+ ASSERT_TRUE((track = GetVideoCodec(*mRecvOff, 4)));
+ ASSERT_EQ("122", track->mDefaultPt);
+ ASSERT_TRUE((track = GetVideoCodec(*mSendAns, 4)));
+ ASSERT_EQ("122", track->mDefaultPt);
+ ASSERT_TRUE((track = GetVideoCodec(*mRecvAns, 4)));
+ ASSERT_EQ("122", track->mDefaultPt);
+}
+
+// Make sure we only put the right things in the fmtp:122 120/.... line
+TEST_F(JsepTrackTest, VideoNegotationOffererAnswererFECMismatch)
+{
+ mOffCodecs.values = MakeCodecs(true, true);
+ mAnsCodecs.values = MakeCodecs(true);
+ // remove h264 from answer codecs
+ ASSERT_EQ("H264", mAnsCodecs.values[3]->mName);
+ mAnsCodecs.values.erase(mAnsCodecs.values.begin()+3);
+
+ InitTracks(SdpMediaSection::kVideo);
+ InitSdp(SdpMediaSection::kVideo);
+ OfferAnswer();
+
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(1);
+
+ ASSERT_NE(mOffer->ToString().find("a=rtpmap:122 red"), std::string::npos);
+ ASSERT_NE(mOffer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
+ ASSERT_NE(mAnswer->ToString().find("a=rtpmap:122 red"), std::string::npos);
+ ASSERT_NE(mAnswer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
+
+ ASSERT_NE(mOffer->ToString().find("a=fmtp:122 120/126/123"), std::string::npos);
+ ASSERT_NE(mAnswer->ToString().find("a=fmtp:122 120/123"), std::string::npos);
+
+ const JsepVideoCodecDescription* track = nullptr;
+ ASSERT_TRUE((track = GetVideoCodec(*mSendOff, 3)));
+ ASSERT_EQ("122", track->mDefaultPt);
+ ASSERT_TRUE((track = GetVideoCodec(*mRecvOff, 3)));
+ ASSERT_EQ("122", track->mDefaultPt);
+ ASSERT_TRUE((track = GetVideoCodec(*mSendAns, 3)));
+ ASSERT_EQ("122", track->mDefaultPt);
+ ASSERT_TRUE((track = GetVideoCodec(*mRecvAns, 3)));
+ ASSERT_EQ("122", track->mDefaultPt);
+}
+
+TEST_F(JsepTrackTest, VideoNegotationOffererAnswererFECZeroVP9Codec)
+{
+ mOffCodecs.values = MakeCodecs(true);
+ JsepVideoCodecDescription* vp9 =
+ new JsepVideoCodecDescription("0", "VP9", 90000);
+ vp9->mConstraints.maxFs = 12288;
+ vp9->mConstraints.maxFps = 60;
+ mOffCodecs.values.push_back(vp9);
+
+ ASSERT_EQ(8U, mOffCodecs.values.size());
+ JsepVideoCodecDescription* red =
+ static_cast<JsepVideoCodecDescription*>(mOffCodecs.values[4]);
+ ASSERT_EQ("red", red->mName);
+ // rebuild the redundant encodings with our newly added "wacky" VP9
+ red->mRedundantEncodings.clear();
+ red->UpdateRedundantEncodings(mOffCodecs.values);
+
+ mAnsCodecs.values = MakeCodecs(true);
+
+ InitTracks(SdpMediaSection::kVideo);
+ InitSdp(SdpMediaSection::kVideo);
+ OfferAnswer();
+
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(1);
+
+ ASSERT_NE(mOffer->ToString().find("a=rtpmap:122 red"), std::string::npos);
+ ASSERT_NE(mOffer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
+ ASSERT_NE(mAnswer->ToString().find("a=rtpmap:122 red"), std::string::npos);
+ ASSERT_NE(mAnswer->ToString().find("a=rtpmap:123 ulpfec"), std::string::npos);
+
+ ASSERT_NE(mOffer->ToString().find("a=fmtp:122 120/126/123/0"), std::string::npos);
+ ASSERT_NE(mAnswer->ToString().find("a=fmtp:122 120/126/123\r\n"), std::string::npos);
+}
+
+TEST_F(JsepTrackTest, VideoNegotiationOfferRemb)
+{
+ InitCodecs();
+ // enable remb on the offer codecs
+ ((JsepVideoCodecDescription*)mOffCodecs.values[2])->EnableRemb();
+ InitTracks(SdpMediaSection::kVideo);
+ InitSdp(SdpMediaSection::kVideo);
+ OfferAnswer();
+
+ // make sure REMB is on offer and not on answer
+ ASSERT_NE(mOffer->ToString().find("a=rtcp-fb:120 goog-remb"),
+ std::string::npos);
+ ASSERT_EQ(mAnswer->ToString().find("a=rtcp-fb:120 goog-remb"),
+ std::string::npos);
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(1);
+
+ CheckOtherFbsSize(*mSendOff, 0);
+ CheckOtherFbsSize(*mRecvAns, 0);
+
+ CheckOtherFbsSize(*mSendAns, 0);
+ CheckOtherFbsSize(*mRecvOff, 0);
+}
+
+TEST_F(JsepTrackTest, VideoNegotiationAnswerRemb)
+{
+ InitCodecs();
+ // enable remb on the answer codecs
+ ((JsepVideoCodecDescription*)mAnsCodecs.values[2])->EnableRemb();
+ InitTracks(SdpMediaSection::kVideo);
+ InitSdp(SdpMediaSection::kVideo);
+ OfferAnswer();
+
+ // make sure REMB is not on offer and not on answer
+ ASSERT_EQ(mOffer->ToString().find("a=rtcp-fb:120 goog-remb"),
+ std::string::npos);
+ ASSERT_EQ(mAnswer->ToString().find("a=rtcp-fb:120 goog-remb"),
+ std::string::npos);
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(1);
+
+ CheckOtherFbsSize(*mSendOff, 0);
+ CheckOtherFbsSize(*mRecvAns, 0);
+
+ CheckOtherFbsSize(*mSendAns, 0);
+ CheckOtherFbsSize(*mRecvOff, 0);
+}
+
+TEST_F(JsepTrackTest, VideoNegotiationOfferAnswerRemb)
+{
+ InitCodecs();
+ // enable remb on the offer and answer codecs
+ ((JsepVideoCodecDescription*)mOffCodecs.values[2])->EnableRemb();
+ ((JsepVideoCodecDescription*)mAnsCodecs.values[2])->EnableRemb();
+ InitTracks(SdpMediaSection::kVideo);
+ InitSdp(SdpMediaSection::kVideo);
+ OfferAnswer();
+
+ // make sure REMB is on offer and on answer
+ ASSERT_NE(mOffer->ToString().find("a=rtcp-fb:120 goog-remb"),
+ std::string::npos);
+ ASSERT_NE(mAnswer->ToString().find("a=rtcp-fb:120 goog-remb"),
+ std::string::npos);
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(1);
+
+ CheckOtherFbsSize(*mSendOff, 1);
+ CheckOtherFbsSize(*mRecvAns, 1);
+ CheckOtherFbExists(*mSendOff, SdpRtcpFbAttributeList::kRemb);
+ CheckOtherFbExists(*mRecvAns, SdpRtcpFbAttributeList::kRemb);
+
+ CheckOtherFbsSize(*mSendAns, 1);
+ CheckOtherFbsSize(*mRecvOff, 1);
+ CheckOtherFbExists(*mSendAns, SdpRtcpFbAttributeList::kRemb);
+ CheckOtherFbExists(*mRecvOff, SdpRtcpFbAttributeList::kRemb);
+}
+
+TEST_F(JsepTrackTest, AudioOffSendonlyAnsRecvonly)
+{
+ Init(SdpMediaSection::kAudio);
+ mRecvOff = nullptr;
+ mSendAns = nullptr;
+ OfferAnswer();
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(0);
+}
+
+TEST_F(JsepTrackTest, VideoOffSendonlyAnsRecvonly)
+{
+ Init(SdpMediaSection::kVideo);
+ mRecvOff = nullptr;
+ mSendAns = nullptr;
+ OfferAnswer();
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(0);
+}
+
+TEST_F(JsepTrackTest, AudioOffSendrecvAnsRecvonly)
+{
+ Init(SdpMediaSection::kAudio);
+ mSendAns = nullptr;
+ OfferAnswer();
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(0);
+}
+
+TEST_F(JsepTrackTest, VideoOffSendrecvAnsRecvonly)
+{
+ Init(SdpMediaSection::kVideo);
+ mSendAns = nullptr;
+ OfferAnswer();
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(0);
+}
+
+TEST_F(JsepTrackTest, AudioOffRecvonlyAnsSendrecv)
+{
+ Init(SdpMediaSection::kAudio);
+ mSendOff = nullptr;
+ OfferAnswer();
+ CheckOffEncodingCount(0);
+ CheckAnsEncodingCount(1);
+}
+
+TEST_F(JsepTrackTest, VideoOffRecvonlyAnsSendrecv)
+{
+ Init(SdpMediaSection::kVideo);
+ mSendOff = nullptr;
+ OfferAnswer();
+ CheckOffEncodingCount(0);
+ CheckAnsEncodingCount(1);
+}
+
+TEST_F(JsepTrackTest, AudioOffSendrecvAnsSendonly)
+{
+ Init(SdpMediaSection::kAudio);
+ mRecvAns = nullptr;
+ OfferAnswer();
+ CheckOffEncodingCount(0);
+ CheckAnsEncodingCount(1);
+}
+
+TEST_F(JsepTrackTest, VideoOffSendrecvAnsSendonly)
+{
+ Init(SdpMediaSection::kVideo);
+ mRecvAns = nullptr;
+ OfferAnswer();
+ CheckOffEncodingCount(0);
+ CheckAnsEncodingCount(1);
+}
+
+static JsepTrack::JsConstraints
+MakeConstraints(const std::string& rid, uint32_t maxBitrate)
+{
+ JsepTrack::JsConstraints constraints;
+ constraints.rid = rid;
+ constraints.constraints.maxBr = maxBitrate;
+ return constraints;
+}
+
+TEST_F(JsepTrackTest, SimulcastRejected)
+{
+ Init(SdpMediaSection::kVideo);
+ std::vector<JsepTrack::JsConstraints> constraints;
+ constraints.push_back(MakeConstraints("foo", 40000));
+ constraints.push_back(MakeConstraints("bar", 10000));
+ mSendOff->SetJsConstraints(constraints);
+ OfferAnswer();
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(1);
+}
+
+TEST_F(JsepTrackTest, SimulcastPrevented)
+{
+ Init(SdpMediaSection::kVideo);
+ std::vector<JsepTrack::JsConstraints> constraints;
+ constraints.push_back(MakeConstraints("foo", 40000));
+ constraints.push_back(MakeConstraints("bar", 10000));
+ mSendAns->SetJsConstraints(constraints);
+ OfferAnswer();
+ CheckOffEncodingCount(1);
+ CheckAnsEncodingCount(1);
+}
+
+TEST_F(JsepTrackTest, SimulcastOfferer)
+{
+ Init(SdpMediaSection::kVideo);
+ std::vector<JsepTrack::JsConstraints> constraints;
+ constraints.push_back(MakeConstraints("foo", 40000));
+ constraints.push_back(MakeConstraints("bar", 10000));
+ mSendOff->SetJsConstraints(constraints);
+ CreateOffer();
+ CreateAnswer();
+ // Add simulcast/rid to answer
+ JsepTrack::AddToMsection(constraints, sdp::kRecv, &GetAnswer());
+ Negotiate();
+ ASSERT_TRUE(mSendOff->GetNegotiatedDetails());
+ ASSERT_EQ(2U, mSendOff->GetNegotiatedDetails()->GetEncodingCount());
+ ASSERT_EQ("foo", mSendOff->GetNegotiatedDetails()->GetEncoding(0).mRid);
+ ASSERT_EQ(40000U,
+ mSendOff->GetNegotiatedDetails()->GetEncoding(0).mConstraints.maxBr);
+ ASSERT_EQ("bar", mSendOff->GetNegotiatedDetails()->GetEncoding(1).mRid);
+ ASSERT_EQ(10000U,
+ mSendOff->GetNegotiatedDetails()->GetEncoding(1).mConstraints.maxBr);
+}
+
+TEST_F(JsepTrackTest, SimulcastAnswerer)
+{
+ Init(SdpMediaSection::kVideo);
+ std::vector<JsepTrack::JsConstraints> constraints;
+ constraints.push_back(MakeConstraints("foo", 40000));
+ constraints.push_back(MakeConstraints("bar", 10000));
+ mSendAns->SetJsConstraints(constraints);
+ CreateOffer();
+ // Add simulcast/rid to offer
+ JsepTrack::AddToMsection(constraints, sdp::kRecv, &GetOffer());
+ CreateAnswer();
+ Negotiate();
+ ASSERT_TRUE(mSendAns->GetNegotiatedDetails());
+ ASSERT_EQ(2U, mSendAns->GetNegotiatedDetails()->GetEncodingCount());
+ ASSERT_EQ("foo", mSendAns->GetNegotiatedDetails()->GetEncoding(0).mRid);
+ ASSERT_EQ(40000U,
+ mSendAns->GetNegotiatedDetails()->GetEncoding(0).mConstraints.maxBr);
+ ASSERT_EQ("bar", mSendAns->GetNegotiatedDetails()->GetEncoding(1).mRid);
+ ASSERT_EQ(10000U,
+ mSendAns->GetNegotiatedDetails()->GetEncoding(1).mConstraints.maxBr);
+}
+
+#define VERIFY_OPUS_MAX_PLAYBACK_RATE(track, expectedRate) \
+{ \
+ JsepTrack& copy(track); \
+ ASSERT_TRUE(copy.GetNegotiatedDetails()); \
+ ASSERT_TRUE(copy.GetNegotiatedDetails()->GetEncodingCount()); \
+ for (auto codec : copy.GetNegotiatedDetails()->GetEncoding(0).GetCodecs()) {\
+ if (codec->mName == "opus") { \
+ JsepAudioCodecDescription* audioCodec = \
+ static_cast<JsepAudioCodecDescription*>(codec); \
+ ASSERT_EQ((expectedRate), audioCodec->mMaxPlaybackRate); \
+ } \
+ }; \
+}
+
+#define VERIFY_OPUS_FORCE_MONO(track, expected) \
+{ \
+ JsepTrack& copy(track); \
+ ASSERT_TRUE(copy.GetNegotiatedDetails()); \
+ ASSERT_TRUE(copy.GetNegotiatedDetails()->GetEncodingCount()); \
+ for (auto codec : copy.GetNegotiatedDetails()->GetEncoding(0).GetCodecs()) {\
+ if (codec->mName == "opus") { \
+ JsepAudioCodecDescription* audioCodec = \
+ static_cast<JsepAudioCodecDescription*>(codec); \
+ /* gtest has some compiler warnings when using ASSERT_EQ with booleans. */ \
+ ASSERT_EQ((int)(expected), (int)audioCodec->mForceMono); \
+ } \
+ }; \
+}
+
+TEST_F(JsepTrackTest, DefaultOpusParameters)
+{
+ Init(SdpMediaSection::kAudio);
+ OfferAnswer();
+
+ VERIFY_OPUS_MAX_PLAYBACK_RATE(*mSendOff,
+ SdpFmtpAttributeList::OpusParameters::kDefaultMaxPlaybackRate);
+ VERIFY_OPUS_MAX_PLAYBACK_RATE(*mSendAns,
+ SdpFmtpAttributeList::OpusParameters::kDefaultMaxPlaybackRate);
+ VERIFY_OPUS_MAX_PLAYBACK_RATE(*mRecvOff, 0U);
+ VERIFY_OPUS_FORCE_MONO(*mRecvOff, false);
+ VERIFY_OPUS_MAX_PLAYBACK_RATE(*mRecvAns, 0U);
+ VERIFY_OPUS_FORCE_MONO(*mRecvAns, false);
+}
+
+TEST_F(JsepTrackTest, NonDefaultOpusParameters)
+{
+ InitCodecs();
+ for (auto& codec : mAnsCodecs.values) {
+ if (codec->mName == "opus") {
+ JsepAudioCodecDescription* audioCodec =
+ static_cast<JsepAudioCodecDescription*>(codec);
+ audioCodec->mMaxPlaybackRate = 16000;
+ audioCodec->mForceMono = true;
+ }
+ }
+ InitTracks(SdpMediaSection::kAudio);
+ InitSdp(SdpMediaSection::kAudio);
+ OfferAnswer();
+
+ VERIFY_OPUS_MAX_PLAYBACK_RATE(*mSendOff, 16000U);
+ VERIFY_OPUS_FORCE_MONO(*mSendOff, true);
+ VERIFY_OPUS_MAX_PLAYBACK_RATE(*mSendAns,
+ SdpFmtpAttributeList::OpusParameters::kDefaultMaxPlaybackRate);
+ VERIFY_OPUS_FORCE_MONO(*mSendAns, false);
+ VERIFY_OPUS_MAX_PLAYBACK_RATE(*mRecvOff, 0U);
+ VERIFY_OPUS_FORCE_MONO(*mRecvOff, false);
+ VERIFY_OPUS_MAX_PLAYBACK_RATE(*mRecvAns, 16000U);
+ VERIFY_OPUS_FORCE_MONO(*mRecvAns, true);
+}
+
+} // namespace mozilla
+
+int
+main(int argc, char** argv)
+{
+ // Prevents some log spew
+ ScopedXPCOM xpcom("jsep_track_unittest");
+
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
+
diff --git a/media/webrtc/signaling/test/mediaconduit_unittests.cpp b/media/webrtc/signaling/test/mediaconduit_unittests.cpp
new file mode 100644
index 000000000..f0cf95a47
--- /dev/null
+++ b/media/webrtc/signaling/test/mediaconduit_unittests.cpp
@@ -0,0 +1,1091 @@
+/* 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 <string>
+#include <fstream>
+#include <unistd.h>
+#include <vector>
+#include <math.h>
+
+using namespace std;
+
+#include "mozilla/SyncRunnable.h"
+#include "mozilla/UniquePtr.h"
+#include <MediaConduitInterface.h>
+#include "GmpVideoCodec.h"
+#include "nsIEventTarget.h"
+#include "FakeMediaStreamsImpl.h"
+#include "FakeLogging.h"
+#include "nsThreadUtils.h"
+#include "runnable_utils.h"
+#include "signaling/src/common/EncodingConstraints.h"
+
+#include "FakeIPC.h"
+#include "FakeIPC.cpp"
+
+#define GTEST_HAS_RTTI 0
+#include "gtest/gtest.h"
+#include "gtest_utils.h"
+
+nsCOMPtr<nsIThread> gMainThread;
+nsCOMPtr<nsIThread> gGtestThread;
+bool gTestsComplete = false;
+
+#include "mtransport_test_utils.h"
+MtransportTestUtils *test_utils;
+
+//Video Frame Color
+const int COLOR = 0x80; //Gray
+
+//MWC RNG of George Marsaglia
+//taken from xiph.org
+static int32_t Rz, Rw;
+static inline int32_t fast_rand(void)
+{
+ Rz=36969*(Rz&65535)+(Rz>>16);
+ Rw=18000*(Rw&65535)+(Rw>>16);
+ return (Rz<<16)+Rw;
+}
+
+/**
+ * Global structure to store video test results.
+ */
+struct VideoTestStats
+{
+ int numRawFramesInserted;
+ int numFramesRenderedSuccessfully;
+ int numFramesRenderedWrongly;
+};
+
+VideoTestStats vidStatsGlobal={0,0,0};
+
+/**
+ * A Dummy Video Conduit Tester.
+ * The test-case inserts a 640*480 grey imagerevery 33 milliseconds
+ * to the video-conduit for encoding and transporting.
+ */
+
+class VideoSendAndReceive
+{
+public:
+ VideoSendAndReceive():width(640),
+ height(480),
+ rate(30)
+ {
+ }
+
+ ~VideoSendAndReceive()
+ {
+ }
+
+ void SetDimensions(int w, int h)
+ {
+ width = w;
+ height = h;
+ }
+ void SetRate(int r) {
+ rate = r;
+ }
+ void Init(RefPtr<mozilla::VideoSessionConduit> aSession)
+ {
+ mSession = aSession;
+ mLen = ((width * height) * 3 / 2);
+ mFrame = mozilla::MakeUnique<uint8_t[]>(mLen);
+ memset(mFrame.get(), COLOR, mLen);
+ numFrames = 121;
+ }
+
+ void GenerateAndReadSamples()
+ {
+ do
+ {
+ mSession->SendVideoFrame(reinterpret_cast<unsigned char*>(mFrame.get()),
+ mLen,
+ width,
+ height,
+ mozilla::kVideoI420,
+ 0);
+ PR_Sleep(PR_MillisecondsToInterval(1000/rate));
+ vidStatsGlobal.numRawFramesInserted++;
+ numFrames--;
+ } while(numFrames >= 0);
+ }
+
+private:
+RefPtr<mozilla::VideoSessionConduit> mSession;
+mozilla::UniquePtr<uint8_t[]> mFrame;
+int mLen;
+int width, height;
+int rate;
+int numFrames;
+};
+
+
+
+/**
+ * A Dummy AudioConduit Tester
+ * The test reads PCM samples of a standard test file and
+ * passws to audio-conduit for encoding, RTPfication and
+ * decoding ebery 10 milliseconds.
+ * This decoded samples are read-off the conduit for writing
+ * into output audio file in PCM format.
+ */
+class AudioSendAndReceive
+{
+public:
+ static const unsigned int PLAYOUT_SAMPLE_FREQUENCY; //default is 16000
+ static const unsigned int PLAYOUT_SAMPLE_LENGTH; //default is 160000
+
+ AudioSendAndReceive()
+ {
+ }
+
+ ~AudioSendAndReceive()
+ {
+ }
+
+ void Init(RefPtr<mozilla::AudioSessionConduit> aSession,
+ RefPtr<mozilla::AudioSessionConduit> aOtherSession,
+ std::string fileIn, std::string fileOut)
+ {
+
+ mSession = aSession;
+ mOtherSession = aOtherSession;
+ iFile = fileIn;
+ oFile = fileOut;
+ }
+
+ //Kick start the test
+ void GenerateAndReadSamples();
+
+private:
+
+ RefPtr<mozilla::AudioSessionConduit> mSession;
+ RefPtr<mozilla::AudioSessionConduit> mOtherSession;
+ std::string iFile;
+ std::string oFile;
+
+ int WriteWaveHeader(int rate, int channels, FILE* outFile);
+ int FinishWaveHeader(FILE* outFile);
+ void GenerateMusic(int16_t* buf, int len);
+};
+
+const unsigned int AudioSendAndReceive::PLAYOUT_SAMPLE_FREQUENCY = 16000;
+const unsigned int AudioSendAndReceive::PLAYOUT_SAMPLE_LENGTH = 160000;
+
+int AudioSendAndReceive::WriteWaveHeader(int rate, int channels, FILE* outFile)
+{
+ //Hardcoded for 16 bit samples
+ unsigned char header[] = {
+ // File header
+ 0x52, 0x49, 0x46, 0x46, // 'RIFF'
+ 0x00, 0x00, 0x00, 0x00, // chunk size
+ 0x57, 0x41, 0x56, 0x45, // 'WAVE'
+ // fmt chunk. We always write 16-bit samples.
+ 0x66, 0x6d, 0x74, 0x20, // 'fmt '
+ 0x10, 0x00, 0x00, 0x00, // chunk size
+ 0x01, 0x00, // WAVE_FORMAT_PCM
+ 0xFF, 0xFF, // channels
+ 0xFF, 0xFF, 0xFF, 0xFF, // sample rate
+ 0x00, 0x00, 0x00, 0x00, // data rate
+ 0xFF, 0xFF, // frame size in bytes
+ 0x10, 0x00, // bits per sample
+ // data chunk
+ 0x64, 0x61, 0x74, 0x61, // 'data'
+ 0xFE, 0xFF, 0xFF, 0x7F // chunk size
+ };
+
+#define set_uint16le(buffer, value) \
+ (buffer)[0] = (value) & 0xff; \
+ (buffer)[1] = (value) >> 8;
+#define set_uint32le(buffer, value) \
+ set_uint16le( (buffer), (value) & 0xffff ); \
+ set_uint16le( (buffer) + 2, (value) >> 16 );
+
+ // set dynamic header fields
+ set_uint16le(header + 22, channels);
+ set_uint32le(header + 24, rate);
+ set_uint16le(header + 32, channels*2);
+
+ size_t written = fwrite(header, 1, sizeof(header), outFile);
+ if (written != sizeof(header)) {
+ cerr << "Writing WAV header failed" << endl;
+ return -1;
+ }
+
+ return 0;
+}
+
+// Update the WAVE file header with the written length
+int AudioSendAndReceive::FinishWaveHeader(FILE* outFile)
+{
+ // Measure how much data we've written
+ long end = ftell(outFile);
+ if (end < 16) {
+ cerr << "Couldn't get output file length" << endl;
+ return (end < 0) ? end : -1;
+ }
+
+ // Update the header
+ unsigned char size[4];
+ int err = fseek(outFile, 40, SEEK_SET);
+ if (err < 0) {
+ cerr << "Couldn't seek to WAV file header." << endl;
+ return err;
+ }
+ set_uint32le(size, (end - 44) & 0xffffffff);
+ size_t written = fwrite(size, 1, sizeof(size), outFile);
+ if (written != sizeof(size)) {
+ cerr << "Couldn't write data size to WAV header" << endl;
+ return -1;
+ }
+
+ // Return to the end
+ err = fseek(outFile, 0, SEEK_END);
+ if (err < 0) {
+ cerr << "Couldn't seek to WAV file end." << endl;
+ return err;
+ }
+
+ return 0;
+}
+
+//Code from xiph.org to generate music of predefined length
+void AudioSendAndReceive::GenerateMusic(short* buf, int len)
+{
+ cerr <<" Generating Input Music " << endl;
+ int32_t a1,a2,b1,b2;
+ int32_t c1,c2,d1,d2;
+ int32_t i,j;
+ a1=b1=a2=b2=0;
+ c1=c2=d1=d2=0;
+ j=0;
+ /*60ms silence */
+ for(i=0;i<2880;i++)
+ {
+ buf[i*2]=buf[(i*2)+1]=0;
+ }
+ for(i=2880;i<len-1;i+=2)
+ {
+ int32_t r;
+ int32_t v1,v2;
+ v1=v2=(((j*((j>>12)^((j>>10|j>>12)&26&j>>7)))&128)+128)<<15;
+ r=fast_rand();v1+=r&65535;v1-=r>>16;
+ r=fast_rand();v2+=r&65535;v2-=r>>16;
+ b1=v1-a1+((b1*61+32)>>6);a1=v1;
+ b2=v2-a2+((b2*61+32)>>6);a2=v2;
+ c1=(30*(c1+b1+d1)+32)>>6;d1=b1;
+ c2=(30*(c2+b2+d2)+32)>>6;d2=b2;
+ v1=(c1+128)>>8;
+ v2=(c2+128)>>8;
+ buf[i]=v1>32767?32767:(v1<-32768?-32768:v1);
+ buf[i+1]=v2>32767?32767:(v2<-32768?-32768:v2);
+ if(i%6==0)j++;
+ }
+ cerr << "Generating Input Music Done " << endl;
+}
+
+//Hardcoded for 16 bit samples for now
+void AudioSendAndReceive::GenerateAndReadSamples()
+{
+ auto audioInput = mozilla::MakeUnique<int16_t []>(PLAYOUT_SAMPLE_LENGTH);
+ auto audioOutput = mozilla::MakeUnique<int16_t []>(PLAYOUT_SAMPLE_LENGTH);
+ short* inbuf;
+ int sampleLengthDecoded = 0;
+ unsigned int SAMPLES = (PLAYOUT_SAMPLE_FREQUENCY * 10); //10 seconds
+ int CHANNELS = 1; //mono audio
+ int sampleLengthInBytes = sizeof(int16_t) * PLAYOUT_SAMPLE_LENGTH;
+ //generated audio buffer
+ inbuf = (short *)moz_xmalloc(sizeof(short)*SAMPLES*CHANNELS);
+ memset(audioInput.get(),0,sampleLengthInBytes);
+ memset(audioOutput.get(),0,sampleLengthInBytes);
+ MOZ_ASSERT(SAMPLES <= PLAYOUT_SAMPLE_LENGTH);
+
+ FILE* inFile = fopen( iFile.c_str(), "wb+");
+ if(!inFile) {
+ cerr << "Input File Creation Failed " << endl;
+ free(inbuf);
+ return;
+ }
+
+ FILE* outFile = fopen( oFile.c_str(), "wb+");
+ if(!outFile) {
+ cerr << "Output File Creation Failed " << endl;
+ free(inbuf);
+ fclose(inFile);
+ return;
+ }
+
+ //Create input file with the music
+ WriteWaveHeader(PLAYOUT_SAMPLE_FREQUENCY, 1, inFile);
+ GenerateMusic(inbuf, SAMPLES);
+ fwrite(inbuf,1,SAMPLES*sizeof(inbuf[0])*CHANNELS,inFile);
+ FinishWaveHeader(inFile);
+ fclose(inFile);
+
+ WriteWaveHeader(PLAYOUT_SAMPLE_FREQUENCY, 1, outFile);
+ unsigned int numSamplesReadFromInput = 0;
+ do
+ {
+ if(!memcpy(audioInput.get(), inbuf, sampleLengthInBytes))
+ {
+ free(inbuf);
+ fclose(outFile);
+ return;
+ }
+
+ numSamplesReadFromInput += PLAYOUT_SAMPLE_LENGTH;
+ inbuf += PLAYOUT_SAMPLE_LENGTH;
+
+ mSession->SendAudioFrame(audioInput.get(),
+ PLAYOUT_SAMPLE_LENGTH,
+ PLAYOUT_SAMPLE_FREQUENCY,10);
+
+ PR_Sleep(PR_MillisecondsToInterval(10));
+ mOtherSession->GetAudioFrame(audioOutput.get(), PLAYOUT_SAMPLE_FREQUENCY,
+ 10, sampleLengthDecoded);
+ if(sampleLengthDecoded == 0)
+ {
+ cerr << " Zero length Sample " << endl;
+ }
+
+ int wrote_ = fwrite (audioOutput.get(), 1 , sampleLengthInBytes, outFile);
+ if(wrote_ != sampleLengthInBytes)
+ {
+ cerr << "Couldn't Write " << sampleLengthInBytes << "bytes" << endl;
+ break;
+ }
+ }while(numSamplesReadFromInput < SAMPLES);
+
+ FinishWaveHeader(outFile);
+ free(inbuf);
+ fclose(outFile);
+}
+
+/**
+ * Dummy Video Target for the conduit
+ * This class acts as renderer attached to the video conuit
+ * As of today we just verify if the frames rendered are exactly
+ * the same as frame inserted at the first place
+ */
+class DummyVideoTarget: public mozilla::VideoRenderer
+{
+public:
+ DummyVideoTarget()
+ {
+ }
+
+ virtual ~DummyVideoTarget()
+ {
+ }
+
+
+ void RenderVideoFrame(const unsigned char* buffer,
+ size_t buffer_size,
+ uint32_t y_stride,
+ uint32_t cbcr_stride,
+ uint32_t time_stamp,
+ int64_t render_time,
+ const mozilla::ImageHandle& handle) override
+ {
+ RenderVideoFrame(buffer, buffer_size, time_stamp, render_time, handle);
+ }
+
+ void RenderVideoFrame(const unsigned char* buffer,
+ size_t buffer_size,
+ uint32_t time_stamp,
+ int64_t render_time,
+ const mozilla::ImageHandle& handle) override
+ {
+ //write the frame to the file
+ if(VerifyFrame(buffer, buffer_size) == 0)
+ {
+ vidStatsGlobal.numFramesRenderedSuccessfully++;
+ } else
+ {
+ vidStatsGlobal.numFramesRenderedWrongly++;
+ }
+ }
+
+ void FrameSizeChange(unsigned int, unsigned int, unsigned int) override
+ {
+ //do nothing
+ }
+
+ //This is hardcoded to check if the contents of frame is COLOR
+ // as we set while sending.
+ int VerifyFrame(const unsigned char* buffer, unsigned int buffer_size)
+ {
+ int good = 0;
+ for(int i=0; i < (int) buffer_size; i++)
+ {
+ if(buffer[i] == COLOR)
+ {
+ ++good;
+ }
+ else
+ {
+ --good;
+ }
+ }
+ return 0;
+ }
+
+};
+
+/**
+ * Webrtc Audio and Video External Transport Class
+ * The functions in this class will be invoked by the conduit
+ * when it has RTP/RTCP frame to transmit.
+ * For everty RTP/RTCP frame we receive, we pass it back
+ * to the conduit for eventual decoding and rendering.
+ */
+class WebrtcMediaTransport : public mozilla::TransportInterface
+{
+public:
+ WebrtcMediaTransport():numPkts(0),
+ mAudio(false),
+ mVideo(false)
+ {
+ }
+
+ ~WebrtcMediaTransport()
+ {
+ }
+
+ virtual nsresult SendRtpPacket(const void* data, int len)
+ {
+ ++numPkts;
+ if(mAudio)
+ {
+ mOtherAudioSession->ReceivedRTPPacket(data,len);
+ } else
+ {
+ mOtherVideoSession->ReceivedRTPPacket(data,len);
+ }
+ return NS_OK;
+ }
+
+ virtual nsresult SendRtcpPacket(const void* data, int len)
+ {
+ if(mAudio)
+ {
+ mOtherAudioSession->ReceivedRTCPPacket(data,len);
+ } else
+ {
+ mOtherVideoSession->ReceivedRTCPPacket(data,len);
+ }
+ return NS_OK;
+ }
+
+ //Treat this object as Audio Transport
+ void SetAudioSession(RefPtr<mozilla::AudioSessionConduit> aSession,
+ RefPtr<mozilla::AudioSessionConduit>
+ aOtherSession)
+ {
+ mAudioSession = aSession;
+ mOtherAudioSession = aOtherSession;
+ mAudio = true;
+ }
+
+ // Treat this object as Video Transport
+ void SetVideoSession(RefPtr<mozilla::VideoSessionConduit> aSession,
+ RefPtr<mozilla::VideoSessionConduit>
+ aOtherSession)
+ {
+ mVideoSession = aSession;
+ mOtherVideoSession = aOtherSession;
+ mVideo = true;
+ }
+
+private:
+ RefPtr<mozilla::AudioSessionConduit> mAudioSession;
+ RefPtr<mozilla::VideoSessionConduit> mVideoSession;
+ RefPtr<mozilla::VideoSessionConduit> mOtherVideoSession;
+ RefPtr<mozilla::AudioSessionConduit> mOtherAudioSession;
+ int numPkts;
+ bool mAudio, mVideo;
+};
+
+
+namespace {
+
+class TransportConduitTest : public ::testing::Test
+{
+ public:
+
+ TransportConduitTest()
+ {
+ //input and output file names
+ iAudiofilename = "input.wav";
+ oAudiofilename = "recorded.wav";
+ }
+
+ ~TransportConduitTest()
+ {
+ mozilla::SyncRunnable::DispatchToThread(gMainThread,
+ mozilla::WrapRunnable(
+ this,
+ &TransportConduitTest::SelfDestruct));
+ }
+
+ void SelfDestruct() {
+ mAudioSession = nullptr;
+ mAudioSession2 = nullptr;
+ mAudioTransport = nullptr;
+
+ mVideoSession = nullptr;
+ mVideoSession2 = nullptr;
+ mVideoRenderer = nullptr;
+ mVideoTransport = nullptr;
+ }
+
+ //1. Dump audio samples to dummy external transport
+ void TestDummyAudioAndTransport()
+ {
+ //get pointer to AudioSessionConduit
+ int err=0;
+ mozilla::SyncRunnable::DispatchToThread(gMainThread,
+ WrapRunnableNMRet(&mAudioSession,
+ &mozilla::AudioSessionConduit::Create));
+ if( !mAudioSession )
+ ASSERT_NE(mAudioSession, (void*)nullptr);
+
+ mozilla::SyncRunnable::DispatchToThread(gMainThread,
+ WrapRunnableNMRet(&mAudioSession2,
+ &mozilla::AudioSessionConduit::Create));
+ if( !mAudioSession2 )
+ ASSERT_NE(mAudioSession2, (void*)nullptr);
+
+ WebrtcMediaTransport* xport = new WebrtcMediaTransport();
+ ASSERT_NE(xport, (void*)nullptr);
+ xport->SetAudioSession(mAudioSession, mAudioSession2);
+ mAudioTransport = xport;
+
+ // attach the transport to audio-conduit
+ err = mAudioSession->SetTransmitterTransport(mAudioTransport);
+ ASSERT_EQ(mozilla::kMediaConduitNoError, err);
+ err = mAudioSession2->SetReceiverTransport(mAudioTransport);
+ ASSERT_EQ(mozilla::kMediaConduitNoError, err);
+
+ //configure send and recv codecs on the audio-conduit
+ //mozilla::AudioCodecConfig cinst1(124, "PCMU", 8000, 80, 1, 64000, false);
+ mozilla::AudioCodecConfig cinst1(124, "opus", 48000, 960, 1, 64000, false);
+ mozilla::AudioCodecConfig cinst2(125, "L16", 16000, 320, 1, 256000, false);
+
+ std::vector<mozilla::AudioCodecConfig*> rcvCodecList;
+ rcvCodecList.push_back(&cinst1);
+ rcvCodecList.push_back(&cinst2);
+
+ err = mAudioSession->ConfigureSendMediaCodec(&cinst1);
+ ASSERT_EQ(mozilla::kMediaConduitNoError, err);
+ err = mAudioSession->StartTransmitting();
+ ASSERT_EQ(mozilla::kMediaConduitNoError, err);
+ err = mAudioSession->ConfigureRecvMediaCodecs(rcvCodecList);
+ ASSERT_EQ(mozilla::kMediaConduitNoError, err);
+
+ err = mAudioSession2->ConfigureSendMediaCodec(&cinst1);
+ ASSERT_EQ(mozilla::kMediaConduitNoError, err);
+ err = mAudioSession2->StartTransmitting();
+ ASSERT_EQ(mozilla::kMediaConduitNoError, err);
+ err = mAudioSession2->ConfigureRecvMediaCodecs(rcvCodecList);
+ ASSERT_EQ(mozilla::kMediaConduitNoError, err);
+
+ //start generating samples
+ audioTester.Init(mAudioSession,mAudioSession2, iAudiofilename,oAudiofilename);
+ cerr << " ******************************************************** " << endl;
+ cerr << " Generating Audio Samples " << endl;
+ cerr << " ******************************************************** " << endl;
+ PR_Sleep(PR_SecondsToInterval(2));
+ audioTester.GenerateAndReadSamples();
+ PR_Sleep(PR_SecondsToInterval(2));
+ cerr << " ******************************************************** " << endl;
+ cerr << " Input Audio File " << iAudiofilename << endl;
+ cerr << " Output Audio File " << oAudiofilename << endl;
+ cerr << " ******************************************************** " << endl;
+ }
+
+ //2. Dump audio samples to dummy external transport
+ void TestDummyVideoAndTransport(bool send_vp8 = true, const char *source_file = nullptr)
+ {
+ int err = 0;
+ //get pointer to VideoSessionConduit
+ mozilla::SyncRunnable::DispatchToThread(gMainThread,
+ WrapRunnableNMRet(&mVideoSession,
+ &mozilla::VideoSessionConduit::Create));
+ if( !mVideoSession )
+ ASSERT_NE(mVideoSession, (void*)nullptr);
+
+ // This session is for other one
+ mozilla::SyncRunnable::DispatchToThread(gMainThread,
+ WrapRunnableNMRet(&mVideoSession2,
+ &mozilla::VideoSessionConduit::Create));
+ if( !mVideoSession2 )
+ ASSERT_NE(mVideoSession2,(void*)nullptr);
+
+ if (!send_vp8) {
+ SetGmpCodecs();
+ }
+
+ mVideoRenderer = new DummyVideoTarget();
+ ASSERT_NE(mVideoRenderer, (void*)nullptr);
+
+ WebrtcMediaTransport* xport = new WebrtcMediaTransport();
+ ASSERT_NE(xport, (void*)nullptr);
+ xport->SetVideoSession(mVideoSession,mVideoSession2);
+ mVideoTransport = xport;
+
+ // attach the transport and renderer to video-conduit
+ err = mVideoSession2->AttachRenderer(mVideoRenderer);
+ ASSERT_EQ(mozilla::kMediaConduitNoError, err);
+ err = mVideoSession->SetTransmitterTransport(mVideoTransport);
+ ASSERT_EQ(mozilla::kMediaConduitNoError, err);
+ err = mVideoSession2->SetReceiverTransport(mVideoTransport);
+ ASSERT_EQ(mozilla::kMediaConduitNoError, err);
+
+ mozilla::EncodingConstraints constraints;
+ //configure send and recv codecs on theconduit
+ mozilla::VideoCodecConfig cinst1(120, "VP8", constraints);
+ mozilla::VideoCodecConfig cinst2(124, "I420", constraints);
+
+
+ std::vector<mozilla::VideoCodecConfig* > rcvCodecList;
+ rcvCodecList.push_back(&cinst1);
+ rcvCodecList.push_back(&cinst2);
+
+ err = mVideoSession->ConfigureSendMediaCodec(
+ send_vp8 ? &cinst1 : &cinst2);
+
+ ASSERT_EQ(mozilla::kMediaConduitNoError, err);
+ err = mVideoSession->StartTransmitting();
+ ASSERT_EQ(mozilla::kMediaConduitNoError, err);
+
+ err = mVideoSession2->ConfigureSendMediaCodec(
+ send_vp8 ? &cinst1 : &cinst2);
+ err = mVideoSession2->StartTransmitting();
+ ASSERT_EQ(mozilla::kMediaConduitNoError, err);
+
+ ASSERT_EQ(mozilla::kMediaConduitNoError, err);
+ err = mVideoSession2->ConfigureRecvMediaCodecs(rcvCodecList);
+ ASSERT_EQ(mozilla::kMediaConduitNoError, err);
+
+ //start generating samples
+ cerr << " *************************************************" << endl;
+ cerr << " Starting the Video Sample Generation " << endl;
+ cerr << " *************************************************" << endl;
+ PR_Sleep(PR_SecondsToInterval(2));
+ videoTester.Init(mVideoSession);
+ videoTester.GenerateAndReadSamples();
+ PR_Sleep(PR_SecondsToInterval(2));
+
+ cerr << " **************************************************" << endl;
+ cerr << " Done With The Testing " << endl;
+ cerr << " VIDEO TEST STATS " << endl;
+ cerr << " Num Raw Frames Inserted: "<<
+ vidStatsGlobal.numRawFramesInserted << endl;
+ cerr << " Num Frames Successfully Rendered: "<<
+ vidStatsGlobal.numFramesRenderedSuccessfully << endl;
+ cerr << " Num Frames Wrongly Rendered: "<<
+ vidStatsGlobal.numFramesRenderedWrongly << endl;
+
+ cerr << " Done With The Testing " << endl;
+
+ cerr << " **************************************************" << endl;
+ ASSERT_EQ(0, vidStatsGlobal.numFramesRenderedWrongly);
+ if (send_vp8) {
+ ASSERT_EQ(vidStatsGlobal.numRawFramesInserted,
+ vidStatsGlobal.numFramesRenderedSuccessfully);
+ }
+ else {
+ // Allow some fudge because there seems to be some buffering.
+ // TODO(ekr@rtfm.com): Fix this.
+ ASSERT_GE(vidStatsGlobal.numRawFramesInserted,
+ vidStatsGlobal.numFramesRenderedSuccessfully);
+ ASSERT_LE(vidStatsGlobal.numRawFramesInserted,
+ vidStatsGlobal.numFramesRenderedSuccessfully + 2);
+ }
+ }
+
+ void TestVideoConduitCodecAPI()
+ {
+ int err = 0;
+ RefPtr<mozilla::VideoSessionConduit> videoSession;
+ //get pointer to VideoSessionConduit
+ mozilla::SyncRunnable::DispatchToThread(gMainThread,
+ WrapRunnableNMRet(&videoSession,
+ &mozilla::VideoSessionConduit::Create));
+ if( !videoSession )
+ ASSERT_NE(videoSession, (void*)nullptr);
+
+ //Test Configure Recv Codec APIS
+ cerr << " *************************************************" << endl;
+ cerr << " Test Receive Codec Configuration API Now " << endl;
+ cerr << " *************************************************" << endl;
+
+ std::vector<mozilla::VideoCodecConfig* > rcvCodecList;
+
+ //Same APIs
+ cerr << " *************************************************" << endl;
+ cerr << " 1. Same Codec (VP8) Repeated Twice " << endl;
+ cerr << " *************************************************" << endl;
+
+ mozilla::EncodingConstraints constraints;
+ mozilla::VideoCodecConfig cinst1(120, "VP8", constraints);
+ mozilla::VideoCodecConfig cinst2(120, "VP8", constraints);
+ rcvCodecList.push_back(&cinst1);
+ rcvCodecList.push_back(&cinst2);
+ err = videoSession->ConfigureRecvMediaCodecs(rcvCodecList);
+ EXPECT_NE(err,mozilla::kMediaConduitNoError);
+ rcvCodecList.pop_back();
+ rcvCodecList.pop_back();
+
+
+ PR_Sleep(PR_SecondsToInterval(2));
+ cerr << " *************************************************" << endl;
+ cerr << " 2. Codec With Invalid Payload Names " << endl;
+ cerr << " *************************************************" << endl;
+ cerr << " Setting payload 1 with name: I4201234tttttthhhyyyy89087987y76t567r7756765rr6u6676" << endl;
+ cerr << " Setting payload 2 with name of zero length" << endl;
+
+ mozilla::VideoCodecConfig cinst3(124, "I4201234tttttthhhyyyy89087987y76t567r7756765rr6u6676", constraints);
+ mozilla::VideoCodecConfig cinst4(124, "", constraints);
+
+ rcvCodecList.push_back(&cinst3);
+ rcvCodecList.push_back(&cinst4);
+
+ err = videoSession->ConfigureRecvMediaCodecs(rcvCodecList);
+ EXPECT_TRUE(err != mozilla::kMediaConduitNoError);
+ rcvCodecList.pop_back();
+ rcvCodecList.pop_back();
+
+
+ PR_Sleep(PR_SecondsToInterval(2));
+ cerr << " *************************************************" << endl;
+ cerr << " 3. Null Codec Parameter " << endl;
+ cerr << " *************************************************" << endl;
+
+ rcvCodecList.push_back(0);
+
+ err = videoSession->ConfigureRecvMediaCodecs(rcvCodecList);
+ EXPECT_TRUE(err != mozilla::kMediaConduitNoError);
+ rcvCodecList.pop_back();
+
+ cerr << " *************************************************" << endl;
+ cerr << " Test Send Codec Configuration API Now " << endl;
+ cerr << " *************************************************" << endl;
+
+ cerr << " *************************************************" << endl;
+ cerr << " 1. Same Codec (VP8) Repeated Twice " << endl;
+ cerr << " *************************************************" << endl;
+
+
+ err = videoSession->ConfigureSendMediaCodec(&cinst1);
+ EXPECT_EQ(mozilla::kMediaConduitNoError, err);
+ err = videoSession->StartTransmitting();
+ ASSERT_EQ(mozilla::kMediaConduitNoError, err);
+ err = videoSession->ConfigureSendMediaCodec(&cinst1);
+ EXPECT_EQ(mozilla::kMediaConduitCodecInUse, err);
+ err = videoSession->StartTransmitting();
+ ASSERT_EQ(mozilla::kMediaConduitNoError, err);
+
+
+ cerr << " *************************************************" << endl;
+ cerr << " 2. Codec With Invalid Payload Names " << endl;
+ cerr << " *************************************************" << endl;
+ cerr << " Setting payload with name: I4201234tttttthhhyyyy89087987y76t567r7756765rr6u6676" << endl;
+
+ err = videoSession->ConfigureSendMediaCodec(&cinst3);
+ EXPECT_TRUE(err != mozilla::kMediaConduitNoError);
+
+ cerr << " *************************************************" << endl;
+ cerr << " 3. Null Codec Parameter " << endl;
+ cerr << " *************************************************" << endl;
+
+ err = videoSession->ConfigureSendMediaCodec(nullptr);
+ EXPECT_TRUE(err != mozilla::kMediaConduitNoError);
+
+ mozilla::SyncRunnable::DispatchToThread(gMainThread,
+ WrapRunnable(
+ videoSession.forget().take(),
+ &mozilla::VideoSessionConduit::Release));
+ }
+
+ void DumpMaxFs(int orig_width, int orig_height, int max_fs,
+ int new_width, int new_height)
+ {
+ cerr << "Applying max_fs=" << max_fs << " to input resolution " <<
+ orig_width << "x" << orig_height << endl;
+ cerr << "New resolution: " << new_width << "x" << new_height << endl;
+ cerr << endl;
+ }
+
+ // Calculate new resolution for sending video by applying max-fs constraint.
+ void GetVideoResolutionWithMaxFs(int orig_width, int orig_height, int max_fs,
+ int *new_width, int *new_height)
+ {
+ int err = 0;
+
+ // Get pointer to VideoSessionConduit.
+ mozilla::SyncRunnable::DispatchToThread(gMainThread,
+ WrapRunnableNMRet(&mVideoSession,
+ &mozilla::VideoSessionConduit::Create));
+ if( !mVideoSession )
+ ASSERT_NE(mVideoSession, (void*)nullptr);
+
+ mozilla::EncodingConstraints constraints;
+ constraints.maxFs = max_fs;
+ // Configure send codecs on the conduit.
+ mozilla::VideoCodecConfig cinst1(120, "VP8", constraints);
+
+ err = mVideoSession->ConfigureSendMediaCodec(&cinst1);
+ ASSERT_EQ(mozilla::kMediaConduitNoError, err);
+ err = mVideoSession->StartTransmitting();
+ ASSERT_EQ(mozilla::kMediaConduitNoError, err);
+
+ // Send one frame.
+ MOZ_ASSERT(!(orig_width & 1));
+ MOZ_ASSERT(!(orig_height & 1));
+ int len = ((orig_width * orig_height) * 3 / 2);
+ uint8_t* frame = (uint8_t*) PR_MALLOC(len);
+
+ memset(frame, COLOR, len);
+ mVideoSession->SendVideoFrame((unsigned char*)frame,
+ len,
+ orig_width,
+ orig_height,
+ mozilla::kVideoI420,
+ 0);
+ PR_Free(frame);
+
+ // Get the new resolution as adjusted by the max-fs constraint.
+ *new_width = mVideoSession->SendingWidth();
+ *new_height = mVideoSession->SendingHeight();
+ }
+
+ void TestVideoConduitMaxFs()
+ {
+ int orig_width, orig_height, width, height, max_fs;
+
+ // No limitation.
+ cerr << "Test no max-fs limition" << endl;
+ orig_width = 640;
+ orig_height = 480;
+ max_fs = 0;
+ GetVideoResolutionWithMaxFs(orig_width, orig_height, max_fs, &width, &height);
+ DumpMaxFs(orig_width, orig_height, max_fs, width, height);
+ ASSERT_EQ(width, 640);
+ ASSERT_EQ(height, 480);
+
+ // VGA to QVGA.
+ cerr << "Test resizing from VGA to QVGA" << endl;
+ orig_width = 640;
+ orig_height = 480;
+ max_fs = 300;
+ GetVideoResolutionWithMaxFs(orig_width, orig_height, max_fs, &width, &height);
+ DumpMaxFs(orig_width, orig_height, max_fs, width, height);
+ ASSERT_EQ(width, 320);
+ ASSERT_EQ(height, 240);
+
+ // Extreme input resolution.
+ cerr << "Test extreme input resolution" << endl;
+ orig_width = 3072;
+ orig_height = 100;
+ max_fs = 300;
+ GetVideoResolutionWithMaxFs(orig_width, orig_height, max_fs, &width, &height);
+ DumpMaxFs(orig_width, orig_height, max_fs, width, height);
+ ASSERT_EQ(width, 768);
+ ASSERT_EQ(height, 26);
+
+ // Small max-fs.
+ cerr << "Test small max-fs (case 1)" << endl;
+ orig_width = 8;
+ orig_height = 32;
+ max_fs = 1;
+ GetVideoResolutionWithMaxFs(orig_width, orig_height, max_fs, &width, &height);
+ DumpMaxFs(orig_width, orig_height, max_fs, width, height);
+ ASSERT_EQ(width, 4);
+ ASSERT_EQ(height, 16);
+
+ // Small max-fs.
+ cerr << "Test small max-fs (case 2)" << endl;
+ orig_width = 4;
+ orig_height = 50;
+ max_fs = 1;
+ GetVideoResolutionWithMaxFs(orig_width, orig_height, max_fs, &width, &height);
+ DumpMaxFs(orig_width, orig_height, max_fs, width, height);
+ ASSERT_EQ(width, 2);
+ ASSERT_EQ(height, 16);
+
+ // Small max-fs.
+ cerr << "Test small max-fs (case 3)" << endl;
+ orig_width = 872;
+ orig_height = 136;
+ max_fs = 3;
+ GetVideoResolutionWithMaxFs(orig_width, orig_height, max_fs, &width, &height);
+ DumpMaxFs(orig_width, orig_height, max_fs, width, height);
+ ASSERT_EQ(width, 48);
+ ASSERT_EQ(height, 8);
+
+ // Small max-fs.
+ cerr << "Test small max-fs (case 4)" << endl;
+ orig_width = 160;
+ orig_height = 8;
+ max_fs = 5;
+ GetVideoResolutionWithMaxFs(orig_width, orig_height, max_fs, &width, &height);
+ DumpMaxFs(orig_width, orig_height, max_fs, width, height);
+ ASSERT_EQ(width, 80);
+ ASSERT_EQ(height, 4);
+
+ // Extremely small width and height(see bug 919979).
+ cerr << "Test with extremely small width and height" << endl;
+ orig_width = 2;
+ orig_height = 2;
+ max_fs = 5;
+ GetVideoResolutionWithMaxFs(orig_width, orig_height, max_fs, &width, &height);
+ DumpMaxFs(orig_width, orig_height, max_fs, width, height);
+ ASSERT_EQ(width, 2);
+ ASSERT_EQ(height, 2);
+
+ // Random values.
+ cerr << "Test with random values" << endl;
+ for (int i = 0; i < 30; i++) {
+ cerr << ".";
+ max_fs = rand() % 1000;
+ orig_width = ((rand() % 2000) & ~1) + 2;
+ orig_height = ((rand() % 2000) & ~1) + 2;
+
+ GetVideoResolutionWithMaxFs(orig_width, orig_height, max_fs,
+ &width, &height);
+ if (max_fs > 0 &&
+ ceil(width / 16.) * ceil(height / 16.) > max_fs) {
+ DumpMaxFs(orig_width, orig_height, max_fs, width, height);
+ ADD_FAILURE();
+ }
+ if ((width & 1) || (height & 1)) {
+ DumpMaxFs(orig_width, orig_height, max_fs, width, height);
+ ADD_FAILURE();
+ }
+ }
+ cerr << endl;
+ }
+
+ void SetGmpCodecs() {
+ mExternalEncoder = mozilla::GmpVideoCodec::CreateEncoder();
+ mExternalDecoder = mozilla::GmpVideoCodec::CreateDecoder();
+ mozilla::EncodingConstraints constraints;
+ mozilla::VideoCodecConfig config(124, "H264", constraints);
+ mVideoSession->SetExternalSendCodec(&config, mExternalEncoder);
+ mVideoSession2->SetExternalRecvCodec(&config, mExternalDecoder);
+ }
+
+ private:
+ //Audio Conduit Test Objects
+ RefPtr<mozilla::AudioSessionConduit> mAudioSession;
+ RefPtr<mozilla::AudioSessionConduit> mAudioSession2;
+ RefPtr<mozilla::TransportInterface> mAudioTransport;
+ AudioSendAndReceive audioTester;
+
+ //Video Conduit Test Objects
+ RefPtr<mozilla::VideoSessionConduit> mVideoSession;
+ RefPtr<mozilla::VideoSessionConduit> mVideoSession2;
+ RefPtr<mozilla::VideoRenderer> mVideoRenderer;
+ RefPtr<mozilla::TransportInterface> mVideoTransport;
+ VideoSendAndReceive videoTester;
+
+ mozilla::VideoEncoder* mExternalEncoder;
+ mozilla::VideoDecoder* mExternalDecoder;
+
+ std::string fileToPlay;
+ std::string fileToRecord;
+ std::string iAudiofilename;
+ std::string oAudiofilename;
+};
+
+
+// Test 1: Test Dummy External Xport
+TEST_F(TransportConduitTest, TestDummyAudioWithTransport) {
+ TestDummyAudioAndTransport();
+}
+
+// Test 2: Test Dummy External Xport
+TEST_F(TransportConduitTest, TestDummyVideoWithTransport) {
+ TestDummyVideoAndTransport();
+ }
+
+TEST_F(TransportConduitTest, TestVideoConduitExternalCodec) {
+ TestDummyVideoAndTransport(false);
+}
+
+TEST_F(TransportConduitTest, TestVideoConduitCodecAPI) {
+ TestVideoConduitCodecAPI();
+ }
+
+TEST_F(TransportConduitTest, TestVideoConduitMaxFs) {
+ TestVideoConduitMaxFs();
+ }
+
+} // end namespace
+
+static int test_result;
+bool test_finished = false;
+
+
+
+// This exists to send as an event to trigger shutdown.
+static void tests_complete() {
+ gTestsComplete = true;
+}
+
+// The GTest thread runs this instead of the main thread so it can
+// do things like ASSERT_TRUE_WAIT which you could not do on the main thread.
+static int gtest_main(int argc, char **argv) {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ ::testing::InitGoogleTest(&argc, argv);
+
+ int result = RUN_ALL_TESTS();
+
+ // Set the global shutdown flag and tickle the main thread
+ // The main thread did not go through Init() so calling Shutdown()
+ // on it will not work.
+ gMainThread->Dispatch(mozilla::WrapRunnableNM(tests_complete), NS_DISPATCH_SYNC);
+
+ return result;
+}
+
+int main(int argc, char **argv)
+{
+ // This test can cause intermittent oranges on the builders
+ CHECK_ENVIRONMENT_FLAG("MOZ_WEBRTC_MEDIACONDUIT_TESTS")
+
+ test_utils = new MtransportTestUtils();
+
+ // Set the main thread global which is this thread.
+ nsIThread *thread;
+ NS_GetMainThread(&thread);
+ gMainThread = thread;
+
+ // Now create the GTest thread and run all of the tests on it
+ // When it is complete it will set gTestsComplete
+ NS_NewNamedThread("gtest_thread", &thread);
+ gGtestThread = thread;
+
+ int result;
+ gGtestThread->Dispatch(
+ mozilla::WrapRunnableNMRet(&result, gtest_main, argc, argv), NS_DISPATCH_NORMAL);
+
+ // Here we handle the event queue for dispatches to the main thread
+ // When the GTest thread is complete it will send one more dispatch
+ // with gTestsComplete == true.
+ while (!gTestsComplete && NS_ProcessNextEvent());
+
+ gGtestThread->Shutdown();
+
+ delete test_utils;
+ return test_result;
+}
+
+
+
diff --git a/media/webrtc/signaling/test/mediapipeline_unittest.cpp b/media/webrtc/signaling/test/mediapipeline_unittest.cpp
new file mode 100644
index 000000000..33518218b
--- /dev/null
+++ b/media/webrtc/signaling/test/mediapipeline_unittest.cpp
@@ -0,0 +1,720 @@
+/* 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/. */
+
+// Original author: ekr@rtfm.com
+
+#include <iostream>
+
+#include "sigslot.h"
+
+#include "logging.h"
+#include "nsThreadUtils.h"
+#include "nsXPCOM.h"
+#include "nss.h"
+#include "ssl.h"
+#include "sslproto.h"
+
+#include "dtlsidentity.h"
+#include "mozilla/RefPtr.h"
+#include "FakeMediaStreams.h"
+#include "FakeMediaStreamsImpl.h"
+#include "FakeLogging.h"
+#include "MediaConduitErrors.h"
+#include "MediaConduitInterface.h"
+#include "MediaPipeline.h"
+#include "MediaPipelineFilter.h"
+#include "runnable_utils.h"
+#include "transportflow.h"
+#include "transportlayerloopback.h"
+#include "transportlayerdtls.h"
+#include "mozilla/SyncRunnable.h"
+
+
+#include "mtransport_test_utils.h"
+#include "runnable_utils.h"
+
+#include "webrtc/modules/interface/module_common_types.h"
+
+#include "FakeIPC.h"
+#include "FakeIPC.cpp"
+
+#define GTEST_HAS_RTTI 0
+#include "gtest/gtest.h"
+#include "gtest_utils.h"
+
+#include "TestHarness.h"
+
+using namespace mozilla;
+MOZ_MTLOG_MODULE("mediapipeline")
+
+MtransportTestUtils *test_utils;
+
+namespace {
+
+class TransportInfo {
+ public:
+ TransportInfo() :
+ flow_(nullptr),
+ loopback_(nullptr),
+ dtls_(nullptr) {}
+
+ static void InitAndConnect(TransportInfo &client, TransportInfo &server) {
+ client.Init(true);
+ server.Init(false);
+ client.PushLayers();
+ server.PushLayers();
+ client.Connect(&server);
+ server.Connect(&client);
+ }
+
+ void Init(bool client) {
+ nsresult res;
+
+ flow_ = new TransportFlow();
+ loopback_ = new TransportLayerLoopback();
+ dtls_ = new TransportLayerDtls();
+
+ res = loopback_->Init();
+ if (res != NS_OK) {
+ FreeLayers();
+ }
+ ASSERT_EQ((nsresult)NS_OK, res);
+
+ std::vector<uint16_t> ciphers;
+ ciphers.push_back(SRTP_AES128_CM_HMAC_SHA1_80);
+ dtls_->SetSrtpCiphers(ciphers);
+ dtls_->SetIdentity(DtlsIdentity::Generate());
+ dtls_->SetRole(client ? TransportLayerDtls::CLIENT :
+ TransportLayerDtls::SERVER);
+ dtls_->SetVerificationAllowAll();
+ }
+
+ void PushLayers() {
+ nsresult res;
+
+ nsAutoPtr<std::queue<TransportLayer *> > layers(
+ new std::queue<TransportLayer *>);
+ layers->push(loopback_);
+ layers->push(dtls_);
+ res = flow_->PushLayers(layers);
+ if (res != NS_OK) {
+ FreeLayers();
+ }
+ ASSERT_EQ((nsresult)NS_OK, res);
+ }
+
+ void Connect(TransportInfo* peer) {
+ MOZ_ASSERT(loopback_);
+ MOZ_ASSERT(peer->loopback_);
+
+ loopback_->Connect(peer->loopback_);
+ }
+
+ // Free the memory allocated at the beginning of Init
+ // if failure occurs before layers setup.
+ void FreeLayers() {
+ delete loopback_;
+ loopback_ = nullptr;
+ delete dtls_;
+ dtls_ = nullptr;
+ }
+
+ void Shutdown() {
+ if (loopback_) {
+ loopback_->Disconnect();
+ }
+ loopback_ = nullptr;
+ dtls_ = nullptr;
+ flow_ = nullptr;
+ }
+
+ RefPtr<TransportFlow> flow_;
+ TransportLayerLoopback *loopback_;
+ TransportLayerDtls *dtls_;
+};
+
+class TestAgent {
+ public:
+ TestAgent() :
+ audio_config_(109, "opus", 48000, 960, 2, 64000, false),
+ audio_conduit_(mozilla::AudioSessionConduit::Create()),
+ audio_(),
+ audio_pipeline_() {
+ }
+
+ static void ConnectRtp(TestAgent *client, TestAgent *server) {
+ TransportInfo::InitAndConnect(client->audio_rtp_transport_,
+ server->audio_rtp_transport_);
+ }
+
+ static void ConnectRtcp(TestAgent *client, TestAgent *server) {
+ TransportInfo::InitAndConnect(client->audio_rtcp_transport_,
+ server->audio_rtcp_transport_);
+ }
+
+ static void ConnectBundle(TestAgent *client, TestAgent *server) {
+ TransportInfo::InitAndConnect(client->bundle_transport_,
+ server->bundle_transport_);
+ }
+
+ virtual void CreatePipelines_s(bool aIsRtcpMux) = 0;
+
+ void Start() {
+ nsresult ret;
+
+ MOZ_MTLOG(ML_DEBUG, "Starting");
+
+ mozilla::SyncRunnable::DispatchToThread(
+ test_utils->sts_target(),
+ WrapRunnableRet(&ret, audio_->GetStream(), &Fake_MediaStream::Start));
+
+ ASSERT_TRUE(NS_SUCCEEDED(ret));
+ }
+
+ void StopInt() {
+ audio_->GetStream()->Stop();
+ }
+
+ void Stop() {
+ MOZ_MTLOG(ML_DEBUG, "Stopping");
+
+ if (audio_pipeline_)
+ audio_pipeline_->ShutdownMedia_m();
+
+ mozilla::SyncRunnable::DispatchToThread(
+ test_utils->sts_target(),
+ WrapRunnable(this, &TestAgent::StopInt));
+ }
+
+ void Shutdown_s() {
+ audio_rtp_transport_.Shutdown();
+ audio_rtcp_transport_.Shutdown();
+ bundle_transport_.Shutdown();
+ if (audio_pipeline_)
+ audio_pipeline_->DetachTransport_s();
+ }
+
+ void Shutdown() {
+ if (audio_pipeline_)
+ audio_pipeline_->ShutdownMedia_m();
+
+ mozilla::SyncRunnable::DispatchToThread(
+ test_utils->sts_target(),
+ WrapRunnable(this, &TestAgent::Shutdown_s));
+ }
+
+ uint32_t GetRemoteSSRC() {
+ uint32_t res = 0;
+ audio_conduit_->GetRemoteSSRC(&res);
+ return res;
+ }
+
+ uint32_t GetLocalSSRC() {
+ uint32_t res = 0;
+ audio_conduit_->GetLocalSSRC(&res);
+ return res;
+ }
+
+ int GetAudioRtpCountSent() {
+ return audio_pipeline_->rtp_packets_sent();
+ }
+
+ int GetAudioRtpCountReceived() {
+ return audio_pipeline_->rtp_packets_received();
+ }
+
+ int GetAudioRtcpCountSent() {
+ return audio_pipeline_->rtcp_packets_sent();
+ }
+
+ int GetAudioRtcpCountReceived() {
+ return audio_pipeline_->rtcp_packets_received();
+ }
+
+ protected:
+ mozilla::AudioCodecConfig audio_config_;
+ RefPtr<mozilla::MediaSessionConduit> audio_conduit_;
+ RefPtr<DOMMediaStream> audio_;
+ // TODO(bcampen@mozilla.com): Right now this does not let us test RTCP in
+ // both directions; only the sender's RTCP is sent, but the receiver should
+ // be sending it too.
+ RefPtr<mozilla::MediaPipeline> audio_pipeline_;
+ TransportInfo audio_rtp_transport_;
+ TransportInfo audio_rtcp_transport_;
+ TransportInfo bundle_transport_;
+};
+
+class TestAgentSend : public TestAgent {
+ public:
+ TestAgentSend() : use_bundle_(false) {}
+
+ virtual void CreatePipelines_s(bool aIsRtcpMux) {
+ audio_ = new Fake_DOMMediaStream(new Fake_AudioStreamSource());
+ audio_->SetHintContents(Fake_DOMMediaStream::HINT_CONTENTS_AUDIO);
+
+ nsTArray<RefPtr<Fake_MediaStreamTrack>> tracks;
+ audio_->GetAudioTracks(tracks);
+ ASSERT_EQ(1U, tracks.Length());
+
+ mozilla::MediaConduitErrorCode err =
+ static_cast<mozilla::AudioSessionConduit *>(audio_conduit_.get())->
+ ConfigureSendMediaCodec(&audio_config_);
+ EXPECT_EQ(mozilla::kMediaConduitNoError, err);
+
+ std::string test_pc("PC");
+
+ if (aIsRtcpMux) {
+ ASSERT_FALSE(audio_rtcp_transport_.flow_);
+ }
+
+ RefPtr<TransportFlow> rtp(audio_rtp_transport_.flow_);
+ RefPtr<TransportFlow> rtcp(audio_rtcp_transport_.flow_);
+
+ if (use_bundle_) {
+ rtp = bundle_transport_.flow_;
+ rtcp = nullptr;
+ }
+
+ audio_pipeline_ = new mozilla::MediaPipelineTransmit(
+ test_pc,
+ nullptr,
+ test_utils->sts_target(),
+ tracks[0],
+ "audio_track_fake_uuid",
+ 1,
+ audio_conduit_,
+ rtp,
+ rtcp,
+ nsAutoPtr<MediaPipelineFilter>());
+
+ audio_pipeline_->Init();
+ }
+
+ void SetUsingBundle(bool use_bundle) {
+ use_bundle_ = use_bundle;
+ }
+
+ private:
+ bool use_bundle_;
+};
+
+
+class TestAgentReceive : public TestAgent {
+ public:
+ virtual void CreatePipelines_s(bool aIsRtcpMux) {
+ mozilla::SourceMediaStream *audio = new Fake_SourceMediaStream();
+ audio->SetPullEnabled(true);
+
+ mozilla::AudioSegment* segment= new mozilla::AudioSegment();
+ audio->AddAudioTrack(0, 100, 0, segment);
+ audio->AdvanceKnownTracksTime(mozilla::STREAM_TIME_MAX);
+
+ audio_ = new Fake_DOMMediaStream(audio);
+
+ std::vector<mozilla::AudioCodecConfig *> codecs;
+ codecs.push_back(&audio_config_);
+
+ mozilla::MediaConduitErrorCode err =
+ static_cast<mozilla::AudioSessionConduit *>(audio_conduit_.get())->
+ ConfigureRecvMediaCodecs(codecs);
+ EXPECT_EQ(mozilla::kMediaConduitNoError, err);
+
+ std::string test_pc("PC");
+
+ if (aIsRtcpMux) {
+ ASSERT_FALSE(audio_rtcp_transport_.flow_);
+ }
+
+ audio_pipeline_ = new mozilla::MediaPipelineReceiveAudio(
+ test_pc,
+ nullptr,
+ test_utils->sts_target(),
+ audio_->GetStream()->AsSourceStream(), "audio_track_fake_uuid", 1, 1,
+ static_cast<mozilla::AudioSessionConduit *>(audio_conduit_.get()),
+ audio_rtp_transport_.flow_,
+ audio_rtcp_transport_.flow_,
+ bundle_filter_);
+
+ audio_pipeline_->Init();
+ }
+
+ void SetBundleFilter(nsAutoPtr<MediaPipelineFilter> filter) {
+ bundle_filter_ = filter;
+ }
+
+ void UpdateFilter_s(
+ nsAutoPtr<MediaPipelineFilter> filter) {
+ audio_pipeline_->UpdateTransport_s(1,
+ audio_rtp_transport_.flow_,
+ audio_rtcp_transport_.flow_,
+ filter);
+ }
+
+ private:
+ nsAutoPtr<MediaPipelineFilter> bundle_filter_;
+};
+
+
+class MediaPipelineTest : public ::testing::Test {
+ public:
+ ~MediaPipelineTest() {
+ p1_.Stop();
+ p2_.Stop();
+ p1_.Shutdown();
+ p2_.Shutdown();
+ }
+
+ // Setup transport.
+ void InitTransports(bool aIsRtcpMux) {
+ // RTP, p1_ is server, p2_ is client
+ mozilla::SyncRunnable::DispatchToThread(
+ test_utils->sts_target(),
+ WrapRunnableNM(&TestAgent::ConnectRtp, &p2_, &p1_));
+
+ // Create RTCP flows separately if we are not muxing them.
+ if(!aIsRtcpMux) {
+ // RTCP, p1_ is server, p2_ is client
+ mozilla::SyncRunnable::DispatchToThread(
+ test_utils->sts_target(),
+ WrapRunnableNM(&TestAgent::ConnectRtcp, &p2_, &p1_));
+ }
+
+ // BUNDLE, p1_ is server, p2_ is client
+ mozilla::SyncRunnable::DispatchToThread(
+ test_utils->sts_target(),
+ WrapRunnableNM(&TestAgent::ConnectBundle, &p2_, &p1_));
+ }
+
+ // Verify RTP and RTCP
+ void TestAudioSend(bool aIsRtcpMux,
+ nsAutoPtr<MediaPipelineFilter> initialFilter =
+ nsAutoPtr<MediaPipelineFilter>(nullptr),
+ nsAutoPtr<MediaPipelineFilter> refinedFilter =
+ nsAutoPtr<MediaPipelineFilter>(nullptr),
+ unsigned int ms_until_filter_update = 500,
+ unsigned int ms_of_traffic_after_answer = 10000) {
+
+ bool bundle = !!(initialFilter);
+ // We do not support testing bundle without rtcp mux, since that doesn't
+ // make any sense.
+ ASSERT_FALSE(!aIsRtcpMux && bundle);
+
+ p2_.SetBundleFilter(initialFilter);
+
+ // Setup transport flows
+ InitTransports(aIsRtcpMux);
+
+ NS_DispatchToMainThread(
+ WrapRunnable(&p1_, &TestAgent::CreatePipelines_s, aIsRtcpMux),
+ NS_DISPATCH_SYNC);
+
+ mozilla::SyncRunnable::DispatchToThread(
+ test_utils->sts_target(),
+ WrapRunnable(&p2_, &TestAgent::CreatePipelines_s, aIsRtcpMux));
+
+ p2_.Start();
+ p1_.Start();
+
+ if (bundle) {
+ PR_Sleep(ms_until_filter_update);
+
+ // Leaving refinedFilter not set implies we want to just update with
+ // the other side's SSRC
+ if (!refinedFilter) {
+ refinedFilter = new MediaPipelineFilter;
+ // Might not be safe, strictly speaking.
+ refinedFilter->AddRemoteSSRC(p1_.GetLocalSSRC());
+ }
+
+ mozilla::SyncRunnable::DispatchToThread(
+ test_utils->sts_target(),
+ WrapRunnable(&p2_,
+ &TestAgentReceive::UpdateFilter_s,
+ refinedFilter));
+ }
+
+ // wait for some RTP/RTCP tx and rx to happen
+ PR_Sleep(ms_of_traffic_after_answer);
+
+ p1_.Stop();
+ p2_.Stop();
+
+ // wait for any packets in flight to arrive
+ PR_Sleep(100);
+
+ p1_.Shutdown();
+ p2_.Shutdown();
+
+ if (!bundle) {
+ // If we are filtering, allow the test-case to do this checking.
+ ASSERT_GE(p1_.GetAudioRtpCountSent(), 40);
+ ASSERT_EQ(p1_.GetAudioRtpCountReceived(), p2_.GetAudioRtpCountSent());
+ ASSERT_EQ(p1_.GetAudioRtpCountSent(), p2_.GetAudioRtpCountReceived());
+
+ // Calling ShutdownMedia_m on both pipelines does not stop the flow of
+ // RTCP. So, we might be off by one here.
+ ASSERT_LE(p2_.GetAudioRtcpCountReceived(), p1_.GetAudioRtcpCountSent());
+ ASSERT_GE(p2_.GetAudioRtcpCountReceived() + 1, p1_.GetAudioRtcpCountSent());
+ }
+
+ }
+
+ void TestAudioReceiverBundle(bool bundle_accepted,
+ nsAutoPtr<MediaPipelineFilter> initialFilter,
+ nsAutoPtr<MediaPipelineFilter> refinedFilter =
+ nsAutoPtr<MediaPipelineFilter>(nullptr),
+ unsigned int ms_until_answer = 500,
+ unsigned int ms_of_traffic_after_answer = 10000) {
+ TestAudioSend(true,
+ initialFilter,
+ refinedFilter,
+ ms_until_answer,
+ ms_of_traffic_after_answer);
+ }
+protected:
+ TestAgentSend p1_;
+ TestAgentReceive p2_;
+};
+
+class MediaPipelineFilterTest : public ::testing::Test {
+ public:
+ bool Filter(MediaPipelineFilter& filter,
+ int32_t correlator,
+ uint32_t ssrc,
+ uint8_t payload_type) {
+
+ webrtc::RTPHeader header;
+ header.ssrc = ssrc;
+ header.payloadType = payload_type;
+ return filter.Filter(header, correlator);
+ }
+};
+
+TEST_F(MediaPipelineFilterTest, TestConstruct) {
+ MediaPipelineFilter filter;
+}
+
+TEST_F(MediaPipelineFilterTest, TestDefault) {
+ MediaPipelineFilter filter;
+ ASSERT_FALSE(Filter(filter, 0, 233, 110));
+}
+
+TEST_F(MediaPipelineFilterTest, TestSSRCFilter) {
+ MediaPipelineFilter filter;
+ filter.AddRemoteSSRC(555);
+ ASSERT_TRUE(Filter(filter, 0, 555, 110));
+ ASSERT_FALSE(Filter(filter, 0, 556, 110));
+}
+
+#define SSRC(ssrc) \
+ ((ssrc >> 24) & 0xFF), \
+ ((ssrc >> 16) & 0xFF), \
+ ((ssrc >> 8 ) & 0xFF), \
+ (ssrc & 0xFF)
+
+#define REPORT_FRAGMENT(ssrc) \
+ SSRC(ssrc), \
+ 0,0,0,0, \
+ 0,0,0,0, \
+ 0,0,0,0, \
+ 0,0,0,0, \
+ 0,0,0,0
+
+#define RTCP_TYPEINFO(num_rrs, type, size) \
+ 0x80 + num_rrs, type, 0, size
+
+const unsigned char rtcp_sr_s16[] = {
+ // zero rrs, size 6 words
+ RTCP_TYPEINFO(0, MediaPipelineFilter::SENDER_REPORT_T, 6),
+ REPORT_FRAGMENT(16)
+};
+
+const unsigned char rtcp_sr_s16_r17[] = {
+ // one rr, size 12 words
+ RTCP_TYPEINFO(1, MediaPipelineFilter::SENDER_REPORT_T, 12),
+ REPORT_FRAGMENT(16),
+ REPORT_FRAGMENT(17)
+};
+
+const unsigned char unknown_type[] = {
+ RTCP_TYPEINFO(1, 222, 0)
+};
+
+TEST_F(MediaPipelineFilterTest, TestEmptyFilterReport0) {
+ MediaPipelineFilter filter;
+ ASSERT_FALSE(filter.FilterSenderReport(rtcp_sr_s16, sizeof(rtcp_sr_s16)));
+}
+
+TEST_F(MediaPipelineFilterTest, TestFilterReport0) {
+ MediaPipelineFilter filter;
+ filter.AddRemoteSSRC(16);
+ ASSERT_TRUE(filter.FilterSenderReport(rtcp_sr_s16, sizeof(rtcp_sr_s16)));
+}
+
+TEST_F(MediaPipelineFilterTest, TestFilterReport0PTTruncated) {
+ MediaPipelineFilter filter;
+ filter.AddRemoteSSRC(16);
+ const unsigned char data[] = {0x80};
+ ASSERT_FALSE(filter.FilterSenderReport(data, sizeof(data)));
+}
+
+TEST_F(MediaPipelineFilterTest, TestFilterReport0CountTruncated) {
+ MediaPipelineFilter filter;
+ filter.AddRemoteSSRC(16);
+ const unsigned char data[] = {};
+ ASSERT_FALSE(filter.FilterSenderReport(data, sizeof(data)));
+}
+
+TEST_F(MediaPipelineFilterTest, TestFilterReport1SSRCTruncated) {
+ MediaPipelineFilter filter;
+ filter.AddRemoteSSRC(16);
+ const unsigned char sr[] = {
+ RTCP_TYPEINFO(1, MediaPipelineFilter::SENDER_REPORT_T, 12),
+ REPORT_FRAGMENT(16),
+ 0,0,0
+ };
+ ASSERT_TRUE(filter.FilterSenderReport(sr, sizeof(sr)));
+}
+
+TEST_F(MediaPipelineFilterTest, TestFilterReport1BigSSRC) {
+ MediaPipelineFilter filter;
+ filter.AddRemoteSSRC(0x01020304);
+ const unsigned char sr[] = {
+ RTCP_TYPEINFO(1, MediaPipelineFilter::SENDER_REPORT_T, 12),
+ SSRC(0x01020304),
+ REPORT_FRAGMENT(0x11121314)
+ };
+ ASSERT_TRUE(filter.FilterSenderReport(sr, sizeof(sr)));
+}
+
+TEST_F(MediaPipelineFilterTest, TestFilterReportMatch) {
+ MediaPipelineFilter filter;
+ filter.AddRemoteSSRC(16);
+ ASSERT_TRUE(filter.FilterSenderReport(rtcp_sr_s16_r17,
+ sizeof(rtcp_sr_s16_r17)));
+}
+
+TEST_F(MediaPipelineFilterTest, TestFilterReportNoMatch) {
+ MediaPipelineFilter filter;
+ filter.AddRemoteSSRC(17);
+ ASSERT_FALSE(filter.FilterSenderReport(rtcp_sr_s16_r17,
+ sizeof(rtcp_sr_s16_r17)));
+}
+
+TEST_F(MediaPipelineFilterTest, TestFilterUnknownRTCPType) {
+ MediaPipelineFilter filter;
+ ASSERT_FALSE(filter.FilterSenderReport(unknown_type, sizeof(unknown_type)));
+}
+
+TEST_F(MediaPipelineFilterTest, TestCorrelatorFilter) {
+ MediaPipelineFilter filter;
+ filter.SetCorrelator(7777);
+ ASSERT_TRUE(Filter(filter, 7777, 16, 110));
+ ASSERT_FALSE(Filter(filter, 7778, 17, 110));
+ // This should also have resulted in the SSRC 16 being added to the filter
+ ASSERT_TRUE(Filter(filter, 0, 16, 110));
+ ASSERT_FALSE(Filter(filter, 0, 17, 110));
+
+ // rtcp_sr_s16 has 16 as an SSRC
+ ASSERT_TRUE(filter.FilterSenderReport(rtcp_sr_s16, sizeof(rtcp_sr_s16)));
+}
+
+TEST_F(MediaPipelineFilterTest, TestPayloadTypeFilter) {
+ MediaPipelineFilter filter;
+ filter.AddUniquePT(110);
+ ASSERT_TRUE(Filter(filter, 0, 555, 110));
+ ASSERT_FALSE(Filter(filter, 0, 556, 111));
+}
+
+TEST_F(MediaPipelineFilterTest, TestPayloadTypeFilterSSRCUpdate) {
+ MediaPipelineFilter filter;
+ filter.AddUniquePT(110);
+ ASSERT_TRUE(Filter(filter, 0, 16, 110));
+
+ // rtcp_sr_s16 has 16 as an SSRC
+ ASSERT_TRUE(filter.FilterSenderReport(rtcp_sr_s16, sizeof(rtcp_sr_s16)));
+}
+
+TEST_F(MediaPipelineFilterTest, TestSSRCMovedWithCorrelator) {
+ MediaPipelineFilter filter;
+ filter.SetCorrelator(7777);
+ ASSERT_TRUE(Filter(filter, 7777, 555, 110));
+ ASSERT_TRUE(Filter(filter, 0, 555, 110));
+ ASSERT_FALSE(Filter(filter, 7778, 555, 110));
+ ASSERT_FALSE(Filter(filter, 0, 555, 110));
+}
+
+TEST_F(MediaPipelineFilterTest, TestRemoteSDPNoSSRCs) {
+ // If the remote SDP doesn't have SSRCs, right now this is a no-op and
+ // there is no point of even incorporating a filter, but we make the
+ // behavior consistent to avoid confusion.
+ MediaPipelineFilter filter;
+ filter.SetCorrelator(7777);
+ filter.AddUniquePT(111);
+ ASSERT_TRUE(Filter(filter, 7777, 555, 110));
+
+ MediaPipelineFilter filter2;
+
+ filter.Update(filter2);
+
+ // Ensure that the old SSRC still works.
+ ASSERT_TRUE(Filter(filter, 0, 555, 110));
+}
+
+TEST_F(MediaPipelineTest, DISABLED_TestAudioSendNoMux) {
+ TestAudioSend(false);
+}
+
+TEST_F(MediaPipelineTest, DISABLED_TestAudioSendMux) {
+ TestAudioSend(true);
+}
+
+TEST_F(MediaPipelineTest, TestAudioSendBundle) {
+ nsAutoPtr<MediaPipelineFilter> filter(new MediaPipelineFilter);
+ // These durations have to be _extremely_ long to have any assurance that
+ // some RTCP will be sent at all. This is because the first RTCP packet
+ // is sometimes sent before the transports are ready, which causes it to
+ // be dropped.
+ TestAudioReceiverBundle(true,
+ filter,
+ // We do not specify the filter for the remote description, so it will be
+ // set to something sane after a short time.
+ nsAutoPtr<MediaPipelineFilter>(),
+ 10000,
+ 10000);
+
+ // Some packets should have been dropped, but not all
+ ASSERT_GT(p1_.GetAudioRtpCountSent(), p2_.GetAudioRtpCountReceived());
+ ASSERT_GT(p2_.GetAudioRtpCountReceived(), 40);
+ ASSERT_GT(p1_.GetAudioRtcpCountSent(), 1);
+ ASSERT_GT(p1_.GetAudioRtcpCountSent(), p2_.GetAudioRtcpCountReceived());
+ ASSERT_GT(p2_.GetAudioRtcpCountReceived(), 0);
+}
+
+TEST_F(MediaPipelineTest, TestAudioSendEmptyBundleFilter) {
+ nsAutoPtr<MediaPipelineFilter> filter(new MediaPipelineFilter);
+ nsAutoPtr<MediaPipelineFilter> bad_answer_filter(new MediaPipelineFilter);
+ TestAudioReceiverBundle(true, filter, bad_answer_filter);
+ // Filter is empty, so should drop everything.
+ ASSERT_EQ(0, p2_.GetAudioRtpCountReceived());
+}
+
+} // end namespace
+
+
+int main(int argc, char **argv) {
+ ScopedXPCOM xpcom("mediapipeline_unittest");
+ test_utils = new MtransportTestUtils();
+ // Start the tests
+ NSS_NoDB_Init(nullptr);
+ NSS_SetDomesticPolicy();
+ ::testing::InitGoogleTest(&argc, argv);
+
+ int rv = RUN_ALL_TESTS();
+ delete test_utils;
+ return rv;
+}
+
+
+
diff --git a/media/webrtc/signaling/test/moz.build b/media/webrtc/signaling/test/moz.build
new file mode 100644
index 000000000..4d8704de4
--- /dev/null
+++ b/media/webrtc/signaling/test/moz.build
@@ -0,0 +1,33 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+# TODO: bug 1172551 - get these tests working on iOS
+if CONFIG['OS_TARGET'] != 'WINNT' and CONFIG['MOZ_WIDGET_TOOLKIT'] != 'gonk' and CONFIG['MOZ_WIDGET_TOOLKIT'] != 'uikit':
+ GeckoCppUnitTests([
+ 'jsep_session_unittest',
+ 'jsep_track_unittest',
+ 'mediaconduit_unittests',
+ 'sdp_file_parser',
+ 'sdp_unittests',
+ 'signaling_unittests',
+ ])
+
+include('/ipc/chromium/chromium-config.mozbuild')
+include('common.build')
+
+USE_LIBS += [
+ '/media/webrtc/signalingtest/signaling_ecc/ecc',
+ 'mtransport_s',
+]
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
+
+if CONFIG['_MSC_VER']:
+ # This is intended as a temporary workaround to enable warning free building
+ # with VS2015.
+ # reinterpret_cast': conversion from 'DWORD' to 'HANDLE' of greater size
+ CXXFLAGS += ['-wd4312']
diff --git a/media/webrtc/signaling/test/sdp_file_parser.cpp b/media/webrtc/signaling/test/sdp_file_parser.cpp
new file mode 100644
index 000000000..3fb0c2f1c
--- /dev/null
+++ b/media/webrtc/signaling/test/sdp_file_parser.cpp
@@ -0,0 +1,85 @@
+/* -*- 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 <string>
+#include <iostream>
+#include <fstream>
+
+#define GTEST_HAS_RTTI 0
+#include "gtest/gtest.h"
+#include "gtest_utils.h"
+
+// without this include linking fails
+#include "FakeMediaStreamsImpl.h"
+#include "FakeLogging.h"
+
+#include "signaling/src/sdp/SipccSdpParser.h"
+
+#include "FakeIPC.h"
+#include "FakeIPC.cpp"
+
+namespace mozilla {
+
+const std::string kDefaultFilename((char *)"/tmp/sdp.bin");
+std::string filename(kDefaultFilename);
+
+class SdpParseTest : public ::testing::Test
+{
+ public:
+ SdpParseTest() {}
+
+ void ParseSdp(const std::string &sdp) {
+ mSdp = mParser.Parse(sdp);
+ }
+
+ void SerializeSdp() {
+ if (mSdp) {
+ mSdp->Serialize(os);
+ std::cout << "Serialized SDP:" << std::endl <<
+ os.str() << std::endl;;
+ }
+ }
+
+ SipccSdpParser mParser;
+ mozilla::UniquePtr<Sdp> mSdp;
+ std::stringstream os;
+}; // class SdpParseTest
+
+TEST_F(SdpParseTest, parseSdpFromFile)
+{
+ std::ifstream file(filename.c_str(),
+ std::ios::in|std::ios::binary|std::ios::ate);
+ ASSERT_TRUE(file.is_open());
+ std::streampos size = file.tellg();
+ size_t nsize = size;
+ nsize+=1;
+ char *memblock = new char [nsize];
+ memset(memblock, '\0', nsize);
+ file.seekg(0, std::ios::beg);
+ file.read(memblock, size);
+ file.close();
+ std::cout << "Read file " << filename << std::endl;
+ ParseSdp(memblock);
+ std::cout << "Parsed SDP" << std::endl;
+ SerializeSdp();
+ delete[] memblock;
+}
+
+} // End namespace mozilla.
+
+int main(int argc, char **argv)
+{
+ ::testing::InitGoogleTest(&argc, argv);
+
+ if (argc == 2) {
+ mozilla::filename = argv[1];
+ } else if (argc > 2) {
+ std::cerr << "Usage: ./sdp_file_parser [filename]" << std::endl;
+ return(1);
+ }
+
+ return RUN_ALL_TESTS();
+}
diff --git a/media/webrtc/signaling/test/sdp_unittests.cpp b/media/webrtc/signaling/test/sdp_unittests.cpp
new file mode 100644
index 000000000..6d00764ae
--- /dev/null
+++ b/media/webrtc/signaling/test/sdp_unittests.cpp
@@ -0,0 +1,5377 @@
+/* -*- 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 "timecard.h"
+
+#include "CSFLog.h"
+
+#include <string>
+#include <sstream>
+
+#define GTEST_HAS_RTTI 0
+#include "gtest/gtest.h"
+#include "gtest_utils.h"
+
+#include "nspr.h"
+#include "nss.h"
+#include "ssl.h"
+
+#include "nsThreadUtils.h"
+#include "FakeMediaStreams.h"
+#include "FakeMediaStreamsImpl.h"
+#include "FakeLogging.h"
+#include "PeerConnectionImpl.h"
+#include "PeerConnectionCtx.h"
+
+#include "mtransport_test_utils.h"
+MtransportTestUtils *test_utils;
+nsCOMPtr<nsIThread> gThread;
+
+#include "signaling/src/sdp/SipccSdpParser.h"
+#include "signaling/src/sdp/SdpMediaSection.h"
+#include "signaling/src/sdp/SdpAttribute.h"
+
+extern "C" {
+#include "signaling/src/sdp/sipcc/sdp.h"
+#include "signaling/src/sdp/sipcc/sdp_private.h"
+}
+
+#ifdef CRLF
+#undef CRLF
+#endif
+#define CRLF "\r\n"
+
+#include "FakeIPC.h"
+#include "FakeIPC.cpp"
+
+#include "TestHarness.h"
+
+using namespace mozilla;
+
+namespace test {
+
+static bool SetupGlobalThread() {
+ if (!gThread) {
+ nsIThread *thread;
+
+ nsresult rv = NS_NewNamedThread("pseudo-main",&thread);
+ if (NS_FAILED(rv))
+ return false;
+
+ gThread = thread;
+ PeerConnectionCtx::InitializeGlobal(gThread,
+ test_utils->sts_target());
+ }
+ return true;
+}
+
+class SdpTest : public ::testing::Test {
+ public:
+ SdpTest() : sdp_ptr_(nullptr) {
+ }
+
+ ~SdpTest() {
+ sdp_free_description(sdp_ptr_);
+ }
+
+ static void SetUpTestCase() {
+ ASSERT_TRUE(SetupGlobalThread());
+ }
+
+ void SetUp() {
+ final_level_ = 0;
+ sdp_ptr_ = nullptr;
+ }
+
+ static void TearDownTestCase() {
+ if (gThread) {
+ gThread->Shutdown();
+ }
+ gThread = nullptr;
+ }
+
+ void ResetSdp() {
+ if (!sdp_ptr_) {
+ sdp_free_description(sdp_ptr_);
+ }
+
+ sdp_media_e supported_media[] = {
+ SDP_MEDIA_AUDIO,
+ SDP_MEDIA_VIDEO,
+ SDP_MEDIA_APPLICATION,
+ SDP_MEDIA_DATA,
+ SDP_MEDIA_CONTROL,
+ SDP_MEDIA_NAS_RADIUS,
+ SDP_MEDIA_NAS_TACACS,
+ SDP_MEDIA_NAS_DIAMETER,
+ SDP_MEDIA_NAS_L2TP,
+ SDP_MEDIA_NAS_LOGIN,
+ SDP_MEDIA_NAS_NONE,
+ SDP_MEDIA_IMAGE,
+ };
+
+ sdp_conf_options_t *config_p = sdp_init_config();
+ unsigned int i;
+ for (i = 0; i < sizeof(supported_media) / sizeof(sdp_media_e); i++) {
+ sdp_media_supported(config_p, supported_media[i], true);
+ }
+ sdp_nettype_supported(config_p, SDP_NT_INTERNET, true);
+ sdp_addrtype_supported(config_p, SDP_AT_IP4, true);
+ sdp_addrtype_supported(config_p, SDP_AT_IP6, true);
+ sdp_transport_supported(config_p, SDP_TRANSPORT_RTPSAVPF, true);
+ sdp_transport_supported(config_p, SDP_TRANSPORT_UDPTL, true);
+ sdp_require_session_name(config_p, false);
+
+ sdp_ptr_ = sdp_init_description(config_p);
+ if (!sdp_ptr_) {
+ sdp_free_config(config_p);
+ }
+ }
+
+ void ParseSdp(const std::string &sdp_str) {
+ const char *buf = sdp_str.data();
+ ResetSdp();
+ ASSERT_EQ(sdp_parse(sdp_ptr_, buf, sdp_str.size()), SDP_SUCCESS);
+ }
+
+ void InitLocalSdp() {
+ ResetSdp();
+ ASSERT_EQ(sdp_set_version(sdp_ptr_, 0), SDP_SUCCESS);
+ ASSERT_EQ(sdp_set_owner_username(sdp_ptr_, "-"), SDP_SUCCESS);
+ ASSERT_EQ(sdp_set_owner_sessionid(sdp_ptr_, "132954853"), SDP_SUCCESS);
+ ASSERT_EQ(sdp_set_owner_version(sdp_ptr_, "0"), SDP_SUCCESS);
+ ASSERT_EQ(sdp_set_owner_network_type(sdp_ptr_, SDP_NT_INTERNET),
+ SDP_SUCCESS);
+ ASSERT_EQ(sdp_set_owner_address_type(sdp_ptr_, SDP_AT_IP4), SDP_SUCCESS);
+ ASSERT_EQ(sdp_set_owner_address(sdp_ptr_, "198.51.100.7"), SDP_SUCCESS);
+ ASSERT_EQ(sdp_set_session_name(sdp_ptr_, "SDP Unit Test"), SDP_SUCCESS);
+ ASSERT_EQ(sdp_set_time_start(sdp_ptr_, "0"), SDP_SUCCESS);
+ ASSERT_EQ(sdp_set_time_stop(sdp_ptr_, "0"), SDP_SUCCESS);
+ }
+
+ std::string SerializeSdp() {
+ flex_string fs;
+ flex_string_init(&fs);
+ EXPECT_EQ(sdp_build(sdp_ptr_, &fs), SDP_SUCCESS);
+ std::string body(fs.buffer);
+ flex_string_free(&fs);
+ return body;
+ }
+
+ // Returns "level" for new media section
+ int AddNewMedia(sdp_media_e type) {
+ final_level_++;
+ EXPECT_EQ(sdp_insert_media_line(sdp_ptr_, final_level_), SDP_SUCCESS);
+ EXPECT_EQ(sdp_set_conn_nettype(sdp_ptr_, final_level_, SDP_NT_INTERNET),
+ SDP_SUCCESS);
+ EXPECT_EQ(sdp_set_conn_addrtype(sdp_ptr_, final_level_, SDP_AT_IP4),
+ SDP_SUCCESS);
+ EXPECT_EQ(sdp_set_conn_address(sdp_ptr_, final_level_, "198.51.100.7"),
+ SDP_SUCCESS);
+ EXPECT_EQ(sdp_set_media_type(sdp_ptr_, final_level_, SDP_MEDIA_VIDEO),
+ SDP_SUCCESS);
+ EXPECT_EQ(sdp_set_media_transport(sdp_ptr_, final_level_,
+ SDP_TRANSPORT_RTPAVP),
+ SDP_SUCCESS);
+ EXPECT_EQ(sdp_set_media_portnum(sdp_ptr_, final_level_, 12345, 0),
+ SDP_SUCCESS);
+ EXPECT_EQ(sdp_add_media_payload_type(sdp_ptr_, final_level_, 120,
+ SDP_PAYLOAD_NUMERIC),
+ SDP_SUCCESS);
+ return final_level_;
+ }
+
+ uint16_t AddNewRtcpFbAck(int level, sdp_rtcp_fb_ack_type_e type,
+ uint16_t payload = SDP_ALL_PAYLOADS) {
+ uint16_t inst_num = 0;
+ EXPECT_EQ(sdp_add_new_attr(sdp_ptr_, level, 0, SDP_ATTR_RTCP_FB,
+ &inst_num), SDP_SUCCESS);
+ EXPECT_EQ(sdp_attr_set_rtcp_fb_ack(sdp_ptr_, level, payload, inst_num,
+ type), SDP_SUCCESS);
+ return inst_num;
+ }
+
+ uint16_t AddNewRtcpFbNack(int level, sdp_rtcp_fb_nack_type_e type,
+ uint16_t payload = SDP_ALL_PAYLOADS) {
+ uint16_t inst_num = 0;
+ EXPECT_EQ(sdp_add_new_attr(sdp_ptr_, level, 0, SDP_ATTR_RTCP_FB,
+ &inst_num), SDP_SUCCESS);
+ EXPECT_EQ(sdp_attr_set_rtcp_fb_nack(sdp_ptr_, level, payload, inst_num,
+ type), SDP_SUCCESS);
+ return inst_num;
+ }
+
+ uint16_t AddNewRtcpFbTrrInt(int level, uint32_t interval,
+ uint16_t payload = SDP_ALL_PAYLOADS) {
+ uint16_t inst_num = 0;
+ EXPECT_EQ(sdp_add_new_attr(sdp_ptr_, level, 0, SDP_ATTR_RTCP_FB,
+ &inst_num), SDP_SUCCESS);
+ EXPECT_EQ(sdp_attr_set_rtcp_fb_trr_int(sdp_ptr_, level, payload, inst_num,
+ interval), SDP_SUCCESS);
+ return inst_num;
+ }
+
+ uint16_t AddNewRtcpFbRemb(int level,
+ uint16_t payload = SDP_ALL_PAYLOADS) {
+ uint16_t inst_num = 0;
+ EXPECT_EQ(sdp_add_new_attr(sdp_ptr_, level, 0, SDP_ATTR_RTCP_FB,
+ &inst_num), SDP_SUCCESS);
+ EXPECT_EQ(sdp_attr_set_rtcp_fb_remb(sdp_ptr_, level, payload, inst_num
+ ), SDP_SUCCESS);
+ return inst_num;
+ }
+
+ uint16_t AddNewRtcpFbCcm(int level, sdp_rtcp_fb_ccm_type_e type,
+ uint16_t payload = SDP_ALL_PAYLOADS) {
+ uint16_t inst_num = 0;
+ EXPECT_EQ(sdp_add_new_attr(sdp_ptr_, level, 0, SDP_ATTR_RTCP_FB,
+ &inst_num), SDP_SUCCESS);
+ EXPECT_EQ(sdp_attr_set_rtcp_fb_ccm(sdp_ptr_, level, payload, inst_num,
+ type), SDP_SUCCESS);
+ return inst_num;
+ }
+ uint16_t AddNewExtMap(int level, const char* uri) {
+ uint16_t inst_num = 0;
+ EXPECT_EQ(sdp_add_new_attr(sdp_ptr_, level, 0, SDP_ATTR_EXTMAP,
+ &inst_num), SDP_SUCCESS);
+ EXPECT_EQ(sdp_attr_set_extmap(sdp_ptr_, level, inst_num,
+ uri, inst_num), SDP_SUCCESS);
+ return inst_num;
+ }
+
+ uint16_t AddNewFmtpMaxFs(int level, uint32_t max_fs) {
+ uint16_t inst_num = 0;
+ EXPECT_EQ(sdp_add_new_attr(sdp_ptr_, level, 0, SDP_ATTR_FMTP,
+ &inst_num), SDP_SUCCESS);
+ EXPECT_EQ(sdp_attr_set_fmtp_payload_type(sdp_ptr_, level, 0, inst_num,
+ 120), SDP_SUCCESS);
+ EXPECT_EQ(sdp_attr_set_fmtp_max_fs(sdp_ptr_, level, 0, inst_num, max_fs),
+ SDP_SUCCESS);
+ return inst_num;
+ }
+
+ uint16_t AddNewFmtpMaxFr(int level, uint32_t max_fr) {
+ uint16_t inst_num = 0;
+ EXPECT_EQ(sdp_add_new_attr(sdp_ptr_, level, 0, SDP_ATTR_FMTP,
+ &inst_num), SDP_SUCCESS);
+ EXPECT_EQ(sdp_attr_set_fmtp_payload_type(sdp_ptr_, level, 0, inst_num,
+ 120), SDP_SUCCESS);
+ EXPECT_EQ(sdp_attr_set_fmtp_max_fr(sdp_ptr_, level, 0, inst_num, max_fr),
+ SDP_SUCCESS);
+ return inst_num;
+ }
+
+ uint16_t AddNewFmtpMaxFsFr(int level, uint32_t max_fs, uint32_t max_fr) {
+ uint16_t inst_num = 0;
+ EXPECT_EQ(sdp_add_new_attr(sdp_ptr_, level, 0, SDP_ATTR_FMTP,
+ &inst_num), SDP_SUCCESS);
+ EXPECT_EQ(sdp_attr_set_fmtp_payload_type(sdp_ptr_, level, 0, inst_num,
+ 120), SDP_SUCCESS);
+ EXPECT_EQ(sdp_attr_set_fmtp_max_fs(sdp_ptr_, level, 0, inst_num, max_fs),
+ SDP_SUCCESS);
+ EXPECT_EQ(sdp_attr_set_fmtp_max_fr(sdp_ptr_, level, 0, inst_num, max_fr),
+ SDP_SUCCESS);
+ return inst_num;
+ }
+
+ protected:
+ int final_level_;
+ sdp_t *sdp_ptr_;
+};
+
+static const std::string kVideoSdp =
+ "v=0\r\n"
+ "o=- 4294967296 2 IN IP4 127.0.0.1\r\n"
+ "s=SIP Call\r\n"
+ "c=IN IP4 198.51.100.7\r\n"
+ "t=0 0\r\n"
+ "m=video 56436 RTP/SAVPF 120\r\n"
+ "a=rtpmap:120 VP8/90000\r\n";
+
+TEST_F(SdpTest, parseRtcpFbAckRpsi) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:120 ack rpsi\r\n");
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ack(sdp_ptr_, 1, 120, 1),
+ SDP_RTCP_FB_ACK_RPSI);
+}
+
+TEST_F(SdpTest, parseRtcpFbAckApp) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:120 ack app\r\n");
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ack(sdp_ptr_, 1, 120, 1), SDP_RTCP_FB_ACK_APP);
+}
+
+TEST_F(SdpTest, parseRtcpFbAckAppFoo) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:120 ack app foo\r\n");
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ack(sdp_ptr_, 1, 120, 1), SDP_RTCP_FB_ACK_APP);
+}
+
+TEST_F(SdpTest, parseRtcpFbAckFooBar) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:120 ack foo bar\r\n");
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ack(sdp_ptr_, 1, 120, 1),
+ SDP_RTCP_FB_ACK_UNKNOWN);
+}
+
+TEST_F(SdpTest, parseRtcpFbAckFooBarBaz) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:120 ack foo bar baz\r\n");
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ack(sdp_ptr_, 1, 120, 1),
+ SDP_RTCP_FB_ACK_UNKNOWN);
+}
+
+TEST_F(SdpTest, parseRtcpFbNack) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:120 nack\r\n");
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_nack(sdp_ptr_, 1, 120, 1),
+ SDP_RTCP_FB_NACK_BASIC);
+}
+
+TEST_F(SdpTest, parseRtcpFbNackPli) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:120 nack pli\r\n");
+}
+
+TEST_F(SdpTest, parseRtcpFbNackSli) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:120 nack sli\r\n");
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_nack(sdp_ptr_, 1, 120, 1),
+ SDP_RTCP_FB_NACK_SLI);
+}
+
+TEST_F(SdpTest, parseRtcpFbNackRpsi) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:120 nack rpsi\r\n");
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_nack(sdp_ptr_, 1, 120, 1),
+ SDP_RTCP_FB_NACK_RPSI);
+}
+
+TEST_F(SdpTest, parseRtcpFbNackApp) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:120 nack app\r\n");
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_nack(sdp_ptr_, 1, 120, 1),
+ SDP_RTCP_FB_NACK_APP);
+}
+
+TEST_F(SdpTest, parseRtcpFbNackAppFoo) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:120 nack app foo\r\n");
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_nack(sdp_ptr_, 1, 120, 1),
+ SDP_RTCP_FB_NACK_APP);
+}
+
+TEST_F(SdpTest, parseRtcpFbNackAppFooBar) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:120 nack app foo bar\r\n");
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_nack(sdp_ptr_, 1, 120, 1),
+ SDP_RTCP_FB_NACK_APP);
+}
+
+TEST_F(SdpTest, parseRtcpFbNackFooBarBaz) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:120 nack foo bar baz\r\n");
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_nack(sdp_ptr_, 1, 120, 1),
+ SDP_RTCP_FB_NACK_UNKNOWN);
+}
+
+TEST_F(SdpTest, parseRtcpFbRemb) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:120 goog-remb\r\n");
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_remb_enabled(sdp_ptr_, 1, 120), true);
+}
+
+TEST_F(SdpTest, parseRtcpRbRembAllPt) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:* goog-remb\r\n");
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_remb_enabled(sdp_ptr_, 1, SDP_ALL_PAYLOADS),
+ true);
+}
+
+TEST_F(SdpTest, parseRtcpFbTrrInt0) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:120 trr-int 0\r\n");
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_trr_int(sdp_ptr_, 1, 120, 1), 0U);
+}
+
+TEST_F(SdpTest, parseRtcpFbTrrInt123) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:120 trr-int 123\r\n");
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_trr_int(sdp_ptr_, 1, 120, 1), 123U);
+}
+
+TEST_F(SdpTest, parseRtcpFbCcmFir) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:120 ccm fir\r\n");
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ccm(sdp_ptr_, 1, 120, 1), SDP_RTCP_FB_CCM_FIR);
+}
+
+TEST_F(SdpTest, parseRtcpFbCcmTmmbr) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:120 ccm tmmbr\r\n");
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ccm(sdp_ptr_, 1, 120, 1),
+ SDP_RTCP_FB_CCM_TMMBR);
+}
+
+TEST_F(SdpTest, parseRtcpFbCcmTmmbrSmaxpr) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:120 ccm tmmbr smaxpr=456\r\n");
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ccm(sdp_ptr_, 1, 120, 1),
+ SDP_RTCP_FB_CCM_TMMBR);
+}
+
+TEST_F(SdpTest, parseRtcpFbCcmTstr) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:120 ccm tstr\r\n");
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ccm(sdp_ptr_, 1, 120, 1),
+ SDP_RTCP_FB_CCM_TSTR);
+}
+
+TEST_F(SdpTest, parseRtcpFbCcmVbcm) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:120 ccm vbcm 123 456 789\r\n");
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ccm(sdp_ptr_, 1, 120, 1),
+ SDP_RTCP_FB_CCM_VBCM);
+ // We don't currently parse out VBCM submessage types, since we don't have
+ // any use for them.
+}
+
+TEST_F(SdpTest, parseRtcpFbCcmFoo) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:120 ccm foo\r\n");
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ccm(sdp_ptr_, 1, 120, 1),
+ SDP_RTCP_FB_CCM_UNKNOWN);
+}
+
+TEST_F(SdpTest, parseRtcpFbCcmFooBarBaz) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:120 ccm foo bar baz\r\n");
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ccm(sdp_ptr_, 1, 120, 1),
+ SDP_RTCP_FB_CCM_UNKNOWN);
+}
+
+TEST_F(SdpTest, parseRtcpFbFoo) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:120 foo\r\n");
+}
+
+TEST_F(SdpTest, parseRtcpFbFooBar) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:120 foo bar\r\n");
+}
+
+TEST_F(SdpTest, parseRtcpFbFooBarBaz) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:120 foo bar baz\r\n");
+}
+
+static const std::string kVideoSdpWithUnknonwBrokenFtmp =
+ "v=0\r\n"
+ "o=- 4294967296 2 IN IP4 127.0.0.1\r\n"
+ "s=SIP Call\r\n"
+ "c=IN IP4 198.51.100.7\r\n"
+ "t=0 0\r\n"
+ "m=video 56436 RTP/SAVPF 120\r\n"
+ "a=rtpmap:120 VP8/90000\r\n"
+ "a=fmtp:122 unknown=10\n"
+ "a=rtpmap:122 red/90000\r\n";
+
+TEST_F(SdpTest, parseUnknownBrokenFtmp) {
+ ParseSdp(kVideoSdpWithUnknonwBrokenFtmp);
+}
+
+TEST_F(SdpTest, parseRtcpFbKitchenSink) {
+ ParseSdp(kVideoSdp +
+ "a=rtcp-fb:120 ack rpsi\r\n"
+ "a=rtcp-fb:120 ack app\r\n"
+ "a=rtcp-fb:120 ack app foo\r\n"
+ "a=rtcp-fb:120 ack foo bar\r\n"
+ "a=rtcp-fb:120 ack foo bar baz\r\n"
+ "a=rtcp-fb:120 nack\r\n"
+ "a=rtcp-fb:120 nack pli\r\n"
+ "a=rtcp-fb:120 nack sli\r\n"
+ "a=rtcp-fb:120 nack rpsi\r\n"
+ "a=rtcp-fb:120 nack app\r\n"
+ "a=rtcp-fb:120 nack app foo\r\n"
+ "a=rtcp-fb:120 nack app foo bar\r\n"
+ "a=rtcp-fb:120 nack foo bar baz\r\n"
+ "a=rtcp-fb:120 trr-int 0\r\n"
+ "a=rtcp-fb:120 trr-int 123\r\n"
+ "a=rtcp-fb:120 goog-remb\r\n"
+ "a=rtcp-fb:120 ccm fir\r\n"
+ "a=rtcp-fb:120 ccm tmmbr\r\n"
+ "a=rtcp-fb:120 ccm tmmbr smaxpr=456\r\n"
+ "a=rtcp-fb:120 ccm tstr\r\n"
+ "a=rtcp-fb:120 ccm vbcm 123 456 789\r\n"
+ "a=rtcp-fb:120 ccm foo\r\n"
+ "a=rtcp-fb:120 ccm foo bar baz\r\n"
+ "a=rtcp-fb:120 foo\r\n"
+ "a=rtcp-fb:120 foo bar\r\n"
+ "a=rtcp-fb:120 foo bar baz\r\n");
+
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ack(sdp_ptr_, 1, 120, 1), SDP_RTCP_FB_ACK_RPSI);
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ack(sdp_ptr_, 1, 120, 2), SDP_RTCP_FB_ACK_APP);
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ack(sdp_ptr_, 1, 120, 3), SDP_RTCP_FB_ACK_APP);
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ack(sdp_ptr_, 1, 120, 4),
+ SDP_RTCP_FB_ACK_UNKNOWN);
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ack(sdp_ptr_, 1, 120, 5),
+ SDP_RTCP_FB_ACK_UNKNOWN);
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ack(sdp_ptr_, 1, 120, 6),
+ SDP_RTCP_FB_ACK_NOT_FOUND);
+
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_nack(sdp_ptr_, 1, 120, 1),
+ SDP_RTCP_FB_NACK_BASIC);
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_nack(sdp_ptr_, 1, 120, 2),
+ SDP_RTCP_FB_NACK_PLI);
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_nack(sdp_ptr_, 1, 120, 3),
+ SDP_RTCP_FB_NACK_SLI);
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_nack(sdp_ptr_, 1, 120, 4),
+ SDP_RTCP_FB_NACK_RPSI);
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_nack(sdp_ptr_, 1, 120, 5),
+ SDP_RTCP_FB_NACK_APP);
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_nack(sdp_ptr_, 1, 120, 6),
+ SDP_RTCP_FB_NACK_APP);
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_nack(sdp_ptr_, 1, 120, 7),
+ SDP_RTCP_FB_NACK_APP);
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_nack(sdp_ptr_, 1, 120, 8),
+ SDP_RTCP_FB_NACK_UNKNOWN);
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_nack(sdp_ptr_, 1, 120, 9),
+ SDP_RTCP_FB_NACK_NOT_FOUND);
+
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_trr_int(sdp_ptr_, 1, 120, 1), 0U);
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_trr_int(sdp_ptr_, 1, 120, 2), 123U);
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_trr_int(sdp_ptr_, 1, 120, 3), 0xFFFFFFFF);
+
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_remb_enabled(sdp_ptr_, 1, 120), true);
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_remb_enabled(sdp_ptr_, 2, 120), false);
+
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ccm(sdp_ptr_, 1, 120, 1), SDP_RTCP_FB_CCM_FIR);
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ccm(sdp_ptr_, 1, 120, 2),
+ SDP_RTCP_FB_CCM_TMMBR);
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ccm(sdp_ptr_, 1, 120, 3),
+ SDP_RTCP_FB_CCM_TMMBR);
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ccm(sdp_ptr_, 1, 120, 4),
+ SDP_RTCP_FB_CCM_TSTR);
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ccm(sdp_ptr_, 1, 120, 5),
+ SDP_RTCP_FB_CCM_VBCM);
+ // We don't currently parse out VBCM submessage types, since we don't have
+ // any use for them.
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ccm(sdp_ptr_, 1, 120, 6),
+ SDP_RTCP_FB_CCM_UNKNOWN);
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ccm(sdp_ptr_, 1, 120, 7),
+ SDP_RTCP_FB_CCM_UNKNOWN);
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ccm(sdp_ptr_, 1, 120, 8),
+ SDP_RTCP_FB_CCM_NOT_FOUND);
+}
+
+TEST_F(SdpTest, addRtcpFbAckRpsi) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbAck(level, SDP_RTCP_FB_ACK_RPSI, 120);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:120 ack rpsi\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbAckRpsiAllPt) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbAck(level, SDP_RTCP_FB_ACK_RPSI);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:* ack rpsi\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbAckApp) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbAck(level, SDP_RTCP_FB_ACK_APP, 120);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:120 ack app\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbAckAppAllPt) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbAck(level, SDP_RTCP_FB_ACK_APP);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:* ack app\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbNack) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbNack(level, SDP_RTCP_FB_NACK_BASIC, 120);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:120 nack\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbNackAllPt) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbNack(level, SDP_RTCP_FB_NACK_BASIC);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:* nack\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbNackSli) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbNack(level, SDP_RTCP_FB_NACK_SLI, 120);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:120 nack sli\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbNackSliAllPt) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbNack(level, SDP_RTCP_FB_NACK_SLI);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:* nack sli\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbNackPli) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbNack(level, SDP_RTCP_FB_NACK_PLI, 120);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:120 nack pli\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbNackPliAllPt) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbNack(level, SDP_RTCP_FB_NACK_PLI);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:* nack pli\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbNackRpsi) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbNack(level, SDP_RTCP_FB_NACK_RPSI, 120);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:120 nack rpsi\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbNackRpsiAllPt) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbNack(level, SDP_RTCP_FB_NACK_RPSI);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:* nack rpsi\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbNackApp) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbNack(level, SDP_RTCP_FB_NACK_APP, 120);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:120 nack app\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbNackAppAllPt) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbNack(level, SDP_RTCP_FB_NACK_APP);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:* nack app\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbNackRai) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbNack(level, SDP_RTCP_FB_NACK_RAI, 120);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:120 nack rai\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbNackRaiAllPt) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbNack(level, SDP_RTCP_FB_NACK_RAI);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:* nack rai\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbNackTllei) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbNack(level, SDP_RTCP_FB_NACK_TLLEI, 120);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:120 nack tllei\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbNackTlleiAllPt) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbNack(level, SDP_RTCP_FB_NACK_TLLEI);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:* nack tllei\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbNackPslei) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbNack(level, SDP_RTCP_FB_NACK_PSLEI, 120);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:120 nack pslei\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbNackPsleiAllPt) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbNack(level, SDP_RTCP_FB_NACK_PSLEI);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:* nack pslei\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbNackEcn) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbNack(level, SDP_RTCP_FB_NACK_ECN, 120);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:120 nack ecn\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbNackEcnAllPt) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbNack(level, SDP_RTCP_FB_NACK_ECN);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:* nack ecn\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbRemb) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbRemb(level, 120);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:120 goog-remb\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbRembAllPt) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbRemb(level);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:* goog-remb\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbTrrInt) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbTrrInt(level, 12345, 120);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:120 trr-int 12345\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbNackTrrIntAllPt) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbTrrInt(level, 0);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:* trr-int 0\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbCcmFir) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbCcm(level, SDP_RTCP_FB_CCM_FIR, 120);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:120 ccm fir\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbCcmFirAllPt) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbCcm(level, SDP_RTCP_FB_CCM_FIR);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:* ccm fir\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbCcmTmmbr) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbCcm(level, SDP_RTCP_FB_CCM_TMMBR, 120);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:120 ccm tmmbr\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbCcmTmmbrAllPt) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbCcm(level, SDP_RTCP_FB_CCM_TMMBR);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:* ccm tmmbr\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbCcmTstr) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbCcm(level, SDP_RTCP_FB_CCM_TSTR, 120);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:120 ccm tstr\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbCcmTstrAllPt) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbCcm(level, SDP_RTCP_FB_CCM_TSTR);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:* ccm tstr\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbCcmVbcm) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbCcm(level, SDP_RTCP_FB_CCM_VBCM, 120);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:120 ccm vbcm\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addRtcpFbCcmVbcmAllPt) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewRtcpFbCcm(level, SDP_RTCP_FB_CCM_VBCM);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=rtcp-fb:* ccm vbcm\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, parseRtcpFbAllPayloads) {
+ ParseSdp(kVideoSdp + "a=rtcp-fb:* ack rpsi\r\n");
+ for (int i = 0; i < 128; i++) {
+ ASSERT_EQ(sdp_attr_get_rtcp_fb_ack(sdp_ptr_, 1, i, 1),
+ SDP_RTCP_FB_ACK_RPSI);
+ }
+}
+TEST_F(SdpTest, addExtMap) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewExtMap(level, SDP_EXTMAP_AUDIO_LEVEL);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, parseExtMap) {
+ ParseSdp(kVideoSdp +
+ "a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n");
+ ASSERT_STREQ(sdp_attr_get_extmap_uri(sdp_ptr_, 1, 1),
+ SDP_EXTMAP_AUDIO_LEVEL);
+ ASSERT_EQ(sdp_attr_get_extmap_id(sdp_ptr_, 1, 1),
+ 1);
+
+}
+
+TEST_F(SdpTest, parseFmtpBitrate) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 bitrate=400\r\n");
+ ASSERT_EQ(400, sdp_attr_get_fmtp_bitrate_type(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpBitrateWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 bitrate=0\r\n");
+ ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_bitrate_type(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpBitrateWith32001) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 bitrate=32001\r\n");
+ ASSERT_EQ(32001, sdp_attr_get_fmtp_bitrate_type(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpBitrateWith4294967296) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 bitrate=4294967296\r\n");
+ ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_bitrate_type(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpMode) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 mode=200\r\n");
+ ASSERT_EQ(200U, sdp_attr_get_fmtp_mode_for_payload_type(sdp_ptr_, 1, 0, 120));
+}
+
+TEST_F(SdpTest, parseFmtpModeWith4294967295) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 mode=4294967295\r\n");
+ ASSERT_EQ(4294967295, sdp_attr_get_fmtp_mode_for_payload_type(sdp_ptr_, 1, 0, 120));
+}
+
+TEST_F(SdpTest, parseFmtpModeWith4294967296) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 mode=4294967296\r\n");
+ // returns 0 if not found
+ ASSERT_EQ(0U, sdp_attr_get_fmtp_mode_for_payload_type(sdp_ptr_, 1, 0, 120));
+}
+
+TEST_F(SdpTest, parseFmtpQcif) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 qcif=20\r\n");
+ ASSERT_EQ(20, sdp_attr_get_fmtp_qcif(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpQcifWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 qcif=0\r\n");
+ ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_qcif(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpQcifWith33) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 qcif=33\r\n");
+ ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_qcif(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpCif) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 cif=11\r\n");
+ ASSERT_EQ(11, sdp_attr_get_fmtp_cif(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpCifWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 cif=0\r\n");
+ ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_cif(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpCifWith33) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 cif=33\r\n");
+ ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_cif(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpMaxbr) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 maxbr=21\r\n");
+ ASSERT_EQ(21, sdp_attr_get_fmtp_maxbr(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpMaxbrWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 maxbr=0\r\n");
+ ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_maxbr(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpMaxbrWith65536) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 maxbr=65536\r\n");
+ ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_maxbr(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpSqcif) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 sqcif=6\r\n");
+ ASSERT_EQ(6, sdp_attr_get_fmtp_sqcif(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpSqcifWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 sqcif=0\r\n");
+ ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_sqcif(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpSqcifWith33) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 sqcif=33\r\n");
+ ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_sqcif(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpCif4) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 cif4=11\r\n");
+ ASSERT_EQ(11, sdp_attr_get_fmtp_cif4(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpCif4With0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 cif4=0\r\n");
+ ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_cif4(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpCif4With33) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 cif4=33\r\n");
+ ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_cif4(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpCif16) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 cif16=11\r\n");
+ ASSERT_EQ(11, sdp_attr_get_fmtp_cif16(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpCif16With0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 cif16=0\r\n");
+ ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_cif16(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpCif16With33) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 cif16=33\r\n");
+ ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_cif16(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpBpp) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 bpp=7\r\n");
+ ASSERT_EQ(7, sdp_attr_get_fmtp_bpp(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpBppWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 bpp=0\r\n");
+ ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_bpp(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpBppWith65536) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 bpp=65536\r\n");
+ ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_bpp(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpHrd) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 hrd=800\r\n");
+ ASSERT_EQ(800, sdp_attr_get_fmtp_hrd(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpHrdWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 hrd=0\r\n");
+ ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_hrd(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpHrdWith65536) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 hrd=65536\r\n");
+ ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_hrd(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpProfile) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 profile=4\r\n");
+ ASSERT_EQ(4, sdp_attr_get_fmtp_profile(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpProfileWith11) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 profile=11\r\n");
+ ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_profile(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpLevel) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 level=56\r\n");
+ ASSERT_EQ(56, sdp_attr_get_fmtp_level(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpLevelWith101) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 level=101\r\n");
+ ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_level(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpPacketizationMode) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 packetization-mode=1\r\n");
+ uint16_t packetizationMode;
+ ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_pack_mode(sdp_ptr_, 1, 0, 1, &packetizationMode));
+ ASSERT_EQ(1, packetizationMode);
+}
+
+TEST_F(SdpTest, parseFmtpPacketizationModeWith3) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 packetization-mode=3\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_pack_mode(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpInterleavingDepth) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 sprop-interleaving-depth=566\r\n");
+ uint16_t interleavingDepth;
+ ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_interleaving_depth(sdp_ptr_, 1, 0, 1, &interleavingDepth));
+ ASSERT_EQ(566, interleavingDepth);
+}
+
+TEST_F(SdpTest, parseFmtpInterleavingDepthWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 sprop-interleaving-depth=0\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_interleaving_depth(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpInterleavingDepthWith65536) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 sprop-interleaving-depth=65536\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_interleaving_depth(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpDeintBuf) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 sprop-deint-buf-req=4294967295\r\n");
+ uint32_t deintBuf;
+ ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_deint_buf_req(sdp_ptr_, 1, 0, 1, &deintBuf));
+ ASSERT_EQ(4294967295, deintBuf);
+}
+
+TEST_F(SdpTest, parseFmtpDeintBufWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 sprop-deint-buf-req=0\r\n");
+ uint32_t deintBuf;
+ ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_deint_buf_req(sdp_ptr_, 1, 0, 1, &deintBuf));
+ ASSERT_EQ(0U, deintBuf);
+}
+
+TEST_F(SdpTest, parseFmtpDeintBufWith4294967296) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 sprop-deint-buf-req=4294967296\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_deint_buf_req(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpMaxDonDiff) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 sprop-max-don-diff=5678\r\n");
+ uint32_t maxDonDiff;
+ ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_max_don_diff(sdp_ptr_, 1, 0, 1, &maxDonDiff));
+ ASSERT_EQ(5678U, maxDonDiff);
+}
+
+TEST_F(SdpTest, parseFmtpMaxDonDiffWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 sprop-max-don-diff=0\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_max_don_diff(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpMaxDonDiffWith4294967296) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 sprop-max-don-diff=4294967296\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_max_don_diff(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpInitBufTime) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 sprop-init-buf-time=4294967295\r\n");
+ uint32_t initBufTime;
+ ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_init_buf_time(sdp_ptr_, 1, 0, 1, &initBufTime));
+ ASSERT_EQ(4294967295, initBufTime);
+}
+
+TEST_F(SdpTest, parseFmtpInitBufTimeWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 sprop-init-buf-time=0\r\n");
+ uint32_t initBufTime;
+ ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_init_buf_time(sdp_ptr_, 1, 0, 1, &initBufTime));
+ ASSERT_EQ(0U, initBufTime);
+}
+
+TEST_F(SdpTest, parseFmtpInitBufTimeWith4294967296) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 sprop-init-buf-time=4294967296\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_init_buf_time(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpMaxMbps) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 max-mbps=46789\r\n");
+ uint32_t maxMpbs;
+ ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_max_mbps(sdp_ptr_, 1, 0, 1, &maxMpbs));
+ ASSERT_EQ(46789U, maxMpbs);
+}
+
+TEST_F(SdpTest, parseFmtpMaxMbpsWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 max-mbps=0\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_max_mbps(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpMaxMbpsWith4294967296) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 max-mbps=4294967296\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_max_mbps(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpMaxCpb) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 max-cpb=47891\r\n");
+ uint32_t maxCpb;
+ ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_max_cpb(sdp_ptr_, 1, 0, 1, &maxCpb));
+ ASSERT_EQ(47891U, maxCpb);
+}
+
+TEST_F(SdpTest, parseFmtpMaxCpbWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 max-cpb=0\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_max_cpb(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpMaxCpbWith4294967296) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 max-cpb=4294967296\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_max_cpb(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpMaxDpb) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 max-dpb=47892\r\n");
+ uint32_t maxDpb;
+ ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_max_dpb(sdp_ptr_, 1, 0, 1, &maxDpb));
+ ASSERT_EQ(47892U, maxDpb);
+}
+
+TEST_F(SdpTest, parseFmtpMaxDpbWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 max-dpb=0\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_max_dpb(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpMaxDpbWith4294967296) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 max-dpb=4294967296\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_max_dpb(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpMaxBr) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 max-br=47893\r\n");
+ uint32_t maxBr;
+ ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_max_br(sdp_ptr_, 1, 0, 1, &maxBr));
+ ASSERT_EQ(47893U, maxBr);
+}
+
+TEST_F(SdpTest, parseFmtpMaxBrWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 max-br=0\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_max_br(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpMaxBrWith4294967296) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 max-br=4294967296\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_max_br(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpRedundantPicCap) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 redundant-pic-cap=1\r\n");
+ ASSERT_EQ(1, sdp_attr_fmtp_is_redundant_pic_cap(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpRedundantPicCapWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 redundant-pic-cap=0\r\n");
+ ASSERT_EQ(0, sdp_attr_fmtp_is_redundant_pic_cap(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpRedundantPicCapWith2) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 redundant-pic-cap=2\r\n");
+ ASSERT_EQ(0, sdp_attr_fmtp_is_redundant_pic_cap(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpDeintBufCap) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 deint-buf-cap=4294967295\r\n");
+ uint32_t deintBufCap;
+ ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_deint_buf_cap(sdp_ptr_, 1, 0, 1, &deintBufCap));
+ ASSERT_EQ(4294967295, deintBufCap);
+}
+
+TEST_F(SdpTest, parseFmtpDeintBufCapWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 deint-buf-cap=0\r\n");
+ uint32_t deintBufCap;
+ ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_deint_buf_cap(sdp_ptr_, 1, 0, 1, &deintBufCap));
+ ASSERT_EQ(0U, deintBufCap);
+}
+
+TEST_F(SdpTest, parseFmtpDeintBufCapWith4294967296) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 deint-buf-cap=4294967296\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_deint_buf_cap(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpMaxRcmdNaluSize) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 max-rcmd-nalu-size=4294967295\r\n");
+ uint32_t maxRcmdNaluSize;
+ ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_max_rcmd_nalu_size(sdp_ptr_, 1, 0, 1, &maxRcmdNaluSize));
+ ASSERT_EQ(4294967295, maxRcmdNaluSize);
+}
+
+TEST_F(SdpTest, parseFmtpMaxRcmdNaluSizeWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 max-rcmd-nalu-size=0\r\n");
+ uint32_t maxRcmdNaluSize;
+ ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_max_rcmd_nalu_size(sdp_ptr_, 1, 0, 1, &maxRcmdNaluSize));
+ ASSERT_EQ(0U, maxRcmdNaluSize);
+}
+
+TEST_F(SdpTest, parseFmtpMaxRcmdNaluSizeWith4294967296) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 max-rcmd-nalu-size=4294967296\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_max_rcmd_nalu_size(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpParameterAdd) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 parameter-add=1\r\n");
+ ASSERT_EQ(1, sdp_attr_fmtp_is_parameter_add(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpParameterAddWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 parameter-add=0\r\n");
+ ASSERT_EQ(0, sdp_attr_fmtp_is_parameter_add(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpParameterAddWith2) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 parameter-add=2\r\n");
+ ASSERT_EQ(0, sdp_attr_fmtp_is_parameter_add(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpAnnexK) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 K=566\r\n");
+ ASSERT_EQ(566, sdp_attr_get_fmtp_annex_k_val(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpAnnexKWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 K=0\r\n");
+ ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_annex_k_val(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpAnnexKWith65536) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 K=65536\r\n");
+ ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_annex_k_val(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpAnnexN) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 N=4567\r\n");
+ ASSERT_EQ(4567, sdp_attr_get_fmtp_annex_n_val(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpAnnexNWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 N=0\r\n");
+ ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_annex_n_val(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpAnnexNWith65536) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 N=65536\r\n");
+ ASSERT_EQ(SDP_INVALID_VALUE, sdp_attr_get_fmtp_annex_n_val(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpAnnexP) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 P=5678,2\r\n");
+ ASSERT_EQ(5678, sdp_attr_get_fmtp_annex_p_picture_resize(sdp_ptr_, 1, 0, 1));
+ ASSERT_EQ(2, sdp_attr_get_fmtp_annex_p_warp(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpAnnexPWithResize0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 P=0,3\r\n");
+ ASSERT_EQ(0, sdp_attr_get_fmtp_annex_p_picture_resize(sdp_ptr_, 1, 0, 1));
+ ASSERT_EQ(3, sdp_attr_get_fmtp_annex_p_warp(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpAnnexPWithResize65536) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 P=65536,4\r\n");
+ ASSERT_EQ(0, sdp_attr_get_fmtp_annex_p_picture_resize(sdp_ptr_, 1, 0, 1));
+ // if the first fails, the second will too. Both default to 0 on failure.
+ ASSERT_EQ(0, sdp_attr_get_fmtp_annex_p_warp(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpAnnexPWithWarp65536) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 P=346,65536\r\n");
+ ASSERT_EQ(346, sdp_attr_get_fmtp_annex_p_picture_resize(sdp_ptr_, 1, 0, 1));
+ ASSERT_EQ(0, sdp_attr_get_fmtp_annex_p_warp(sdp_ptr_, 1, 0, 1));
+}
+
+TEST_F(SdpTest, parseFmtpLevelAsymmetryAllowed) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 level-asymmetry-allowed=1\r\n");
+
+ uint16_t levelAsymmetryAllowed;
+ ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_level_asymmetry_allowed(sdp_ptr_, 1, 0, 1, &levelAsymmetryAllowed));
+ ASSERT_EQ(1U, levelAsymmetryAllowed);
+}
+
+TEST_F(SdpTest, parseFmtpLevelAsymmetryAllowedWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 level-asymmetry-allowed=0\r\n");
+ uint16_t levelAsymmetryAllowed;
+ ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_level_asymmetry_allowed(sdp_ptr_, 1, 0, 1, &levelAsymmetryAllowed));
+ ASSERT_EQ(0U, levelAsymmetryAllowed);
+}
+
+TEST_F(SdpTest, parseFmtpLevelAsymmetryAllowedWith2) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 level-asymmetry-allowed=2\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_level_asymmetry_allowed(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpMaxAverageBitrate) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 maxaveragebitrate=47893\r\n");
+ uint32_t maxAverageBitrate;
+ ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_max_average_bitrate(sdp_ptr_, 1, 0, 1, &maxAverageBitrate));
+ ASSERT_EQ(47893U, maxAverageBitrate);
+}
+
+TEST_F(SdpTest, parseFmtpMaxAverageBitrateWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 maxaveragebitrate=0\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_max_average_bitrate(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpMaxAverageBitrateWith4294967296) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 maxaveragebitrate=4294967296\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_max_average_bitrate(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpUsedTx) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 usedtx=1\r\n");
+ tinybool usedTx;
+ ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_usedtx(sdp_ptr_, 1, 0, 1, &usedTx));
+ ASSERT_EQ(1, usedTx);
+}
+
+TEST_F(SdpTest, parseFmtpUsedTxWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 usedtx=0\r\n");
+ tinybool usedTx;
+ ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_usedtx(sdp_ptr_, 1, 0, 1, &usedTx));
+ ASSERT_EQ(0, usedTx);
+}
+
+TEST_F(SdpTest, parseFmtpUsedTxWith2) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 usedtx=2\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_usedtx(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpStereo) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 stereo=1\r\n");
+ tinybool stereo;
+ ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_stereo(sdp_ptr_, 1, 0, 1, &stereo));
+ ASSERT_EQ(1, stereo);
+}
+
+TEST_F(SdpTest, parseFmtpStereoWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 stereo=0\r\n");
+ tinybool stereo;
+ ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_stereo(sdp_ptr_, 1, 0, 1, &stereo));
+ ASSERT_EQ(0, stereo);
+}
+
+TEST_F(SdpTest, parseFmtpStereoWith2) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 stereo=2\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_stereo(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpUseInBandFec) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 useinbandfec=1\r\n");
+ tinybool useInbandFec;
+ ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_useinbandfec(sdp_ptr_, 1, 0, 1, &useInbandFec));
+ ASSERT_EQ(1, useInbandFec);
+}
+
+TEST_F(SdpTest, parseFmtpUseInBandWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 useinbandfec=0\r\n");
+ tinybool useInbandFec;
+ ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_useinbandfec(sdp_ptr_, 1, 0, 1, &useInbandFec));
+ ASSERT_EQ(0, useInbandFec);
+}
+
+TEST_F(SdpTest, parseFmtpUseInBandWith2) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 useinbandfec=2\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_useinbandfec(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpMaxCodedAudioBandwidth) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 maxcodedaudiobandwidth=abcdefg\r\n");
+ char* maxCodedAudioBandwith = sdp_attr_get_fmtp_maxcodedaudiobandwidth(sdp_ptr_, 1, 0, 1);
+ ASSERT_EQ(0, strcmp("abcdefg", maxCodedAudioBandwith));
+}
+
+TEST_F(SdpTest, parseFmtpMaxCodedAudioBandwidthBad) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 maxcodedaudiobandwidth=\r\n");
+ char* maxCodedAudioBandwith = sdp_attr_get_fmtp_maxcodedaudiobandwidth(sdp_ptr_, 1, 0, 1);
+ ASSERT_EQ(0, *maxCodedAudioBandwith);
+}
+
+TEST_F(SdpTest, parseFmtpCbr) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 cbr=1\r\n");
+ tinybool cbr;
+ ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_cbr(sdp_ptr_, 1, 0, 1, &cbr));
+ ASSERT_EQ(1, cbr);
+}
+
+TEST_F(SdpTest, parseFmtpCbrWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 cbr=0\r\n");
+ tinybool cbr;
+ ASSERT_EQ(SDP_SUCCESS, sdp_attr_get_fmtp_cbr(sdp_ptr_, 1, 0, 1, &cbr));
+ ASSERT_EQ(0, cbr);
+}
+
+TEST_F(SdpTest, parseFmtpCbrWith2) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 cbr=2\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_cbr(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpMaxPlaybackRate) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 maxplaybackrate=47900\r\n");
+ sdp_attr_t *attr_p = sdp_find_attr(sdp_ptr_, 1, 0, SDP_ATTR_FMTP, 1);
+ ASSERT_NE(NULL, attr_p);
+ ASSERT_EQ(47900U, attr_p->attr.fmtp.maxplaybackrate);
+}
+
+TEST_F(SdpTest, parseFmtpMaxPlaybackRateWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 maxplaybackrate=0\r\n");
+ sdp_attr_t *attr_p = sdp_find_attr(sdp_ptr_, 1, 0, SDP_ATTR_FMTP, 1);
+ ASSERT_EQ(NULL, attr_p);
+}
+
+TEST_F(SdpTest, parseFmtpMaxPlaybackRateWith4294967296) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 maxplaybackrate=4294967296\r\n");
+ sdp_attr_t *attr_p = sdp_find_attr(sdp_ptr_, 1, 0, SDP_ATTR_FMTP, 1);
+ ASSERT_EQ(NULL, attr_p);
+}
+
+TEST_F(SdpTest, parseFmtpMaxFs) {
+ uint32_t val = 0;
+ ParseSdp(kVideoSdp + "a=fmtp:120 max-fs=300;max-fr=30\r\n");
+ ASSERT_EQ(sdp_attr_get_fmtp_max_fs(sdp_ptr_, 1, 0, 1, &val), SDP_SUCCESS);
+ ASSERT_EQ(val, 300U);
+}
+TEST_F(SdpTest, parseFmtpMaxFsWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 max-fs=0\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_max_fs(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpMaxFsWith4294967296) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 max-fs=4294967296\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_max_fs(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpMaxFr) {
+ uint32_t val = 0;
+ ParseSdp(kVideoSdp + "a=fmtp:120 max-fs=300;max-fr=30\r\n");
+ ASSERT_EQ(sdp_attr_get_fmtp_max_fr(sdp_ptr_, 1, 0, 1, &val), SDP_SUCCESS);
+ ASSERT_EQ(val, 30U);
+}
+
+TEST_F(SdpTest, parseFmtpMaxFrWith0) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 max-fr=0\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_max_fr(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, parseFmtpMaxFrWith4294967296) {
+ ParseSdp(kVideoSdp + "a=fmtp:120 max-fr=4294967296\r\n");
+ ASSERT_EQ(SDP_INVALID_PARAMETER, sdp_attr_get_fmtp_max_fr(sdp_ptr_, 1, 0, 1, nullptr));
+}
+
+TEST_F(SdpTest, addFmtpMaxFs) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewFmtpMaxFs(level, 300);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=fmtp:120 max-fs=300\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addFmtpMaxFr) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewFmtpMaxFr(level, 30);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=fmtp:120 max-fr=30\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, addFmtpMaxFsFr) {
+ InitLocalSdp();
+ int level = AddNewMedia(SDP_MEDIA_VIDEO);
+ AddNewFmtpMaxFsFr(level, 300, 30);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=fmtp:120 max-fs=300;max-fr=30\r\n"),
+ std::string::npos);
+}
+
+static const std::string kBrokenFmtp =
+ "v=0\r\n"
+ "o=- 4294967296 2 IN IP4 127.0.0.1\r\n"
+ "s=SIP Call\r\n"
+ "t=0 0\r\n"
+ "m=video 56436 RTP/SAVPF 120\r\n"
+ "c=IN IP4 198.51.100.7\r\n"
+ "a=rtpmap:120 VP8/90000\r\n"
+ /* Note: the \0 in this string triggered bz://1089207
+ */
+ "a=fmtp:120 max-fs=300;max\0fr=30";
+
+TEST_F(SdpTest, parseBrokenFmtp) {
+ uint32_t val = 0;
+ const char *buf = kBrokenFmtp.data();
+ ResetSdp();
+ /* We need to manually invoke the parser here to be able to specify the length
+ * of the string beyond the \0 in last line of the string.
+ */
+ ASSERT_EQ(sdp_parse(sdp_ptr_, buf, 165), SDP_SUCCESS);
+ ASSERT_EQ(sdp_attr_get_fmtp_max_fs(sdp_ptr_, 1, 0, 1, &val), SDP_INVALID_PARAMETER);
+}
+
+TEST_F(SdpTest, addIceLite) {
+ InitLocalSdp();
+ uint16_t inst_num = 0;
+ EXPECT_EQ(sdp_add_new_attr(sdp_ptr_, SDP_SESSION_LEVEL, 0,
+ SDP_ATTR_ICE_LITE, &inst_num), SDP_SUCCESS);
+ std::string body = SerializeSdp();
+ ASSERT_NE(body.find("a=ice-lite\r\n"), std::string::npos);
+}
+
+TEST_F(SdpTest, parseIceLite) {
+ std::string sdp =
+ "v=0\r\n"
+ "o=- 4294967296 2 IN IP4 127.0.0.1\r\n"
+ "s=SIP Call\r\n"
+ "t=0 0\r\n"
+ "a=ice-lite\r\n";
+ ParseSdp(sdp);
+ ASSERT_TRUE(sdp_attr_is_present(sdp_ptr_, SDP_ATTR_ICE_LITE,
+ SDP_SESSION_LEVEL, 0));
+}
+
+class NewSdpTest : public ::testing::Test,
+ public ::testing::WithParamInterface<bool> {
+ public:
+ NewSdpTest() {}
+
+ void ParseSdp(const std::string &sdp, bool expectSuccess = true) {
+ mSdp = mozilla::Move(mParser.Parse(sdp));
+
+ // Are we configured to do a parse and serialize before actually
+ // running the test?
+ if (GetParam()) {
+ std::stringstream os;
+
+ if (expectSuccess) {
+ ASSERT_TRUE(!!mSdp) << "Parse failed on first pass: "
+ << GetParseErrors();
+ }
+
+ if (mSdp) {
+ // Serialize and re-parse
+ mSdp->Serialize(os);
+ mSdp = mozilla::Move(mParser.Parse(os.str()));
+
+ // Whether we expected the parse to work or not, it should
+ // succeed the second time if it succeeded the first.
+ ASSERT_TRUE(!!mSdp) << "Parse failed on second pass, SDP was: "
+ << std::endl << os.str() << std::endl
+ << "Errors were: " << GetParseErrors();
+
+ // Serialize again and compare
+ std::stringstream os2;
+ mSdp->Serialize(os2);
+ ASSERT_EQ(os.str(), os2.str());
+ }
+ }
+
+ if (expectSuccess) {
+ ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+ ASSERT_EQ(0U, mParser.GetParseErrors().size())
+ << "Got unexpected parse errors/warnings: "
+ << GetParseErrors();
+ }
+ }
+
+ // For streaming parse errors
+ std::string GetParseErrors() const {
+ std::stringstream output;
+ for (auto e = mParser.GetParseErrors().begin();
+ e != mParser.GetParseErrors().end();
+ ++e) {
+ output << e->first << ": " << e->second << std::endl;
+ }
+ return output.str();
+ }
+
+ void CheckRtpmap(const std::string& expected_pt,
+ SdpRtpmapAttributeList::CodecType codec,
+ const std::string& name,
+ uint32_t clock,
+ uint16_t channels,
+ const std::string& search_pt,
+ const SdpRtpmapAttributeList& rtpmaps) const {
+ ASSERT_TRUE(rtpmaps.HasEntry(search_pt));
+ auto attr = rtpmaps.GetEntry(search_pt);
+ ASSERT_EQ(expected_pt, attr.pt);
+ ASSERT_EQ(codec, attr.codec);
+ ASSERT_EQ(name, attr.name);
+ ASSERT_EQ(clock, attr.clock);
+ ASSERT_EQ(channels, attr.channels);
+ }
+
+ void CheckSctpmap(const std::string& expected_pt,
+ const std::string& name,
+ uint16_t streams,
+ const std::string& search_pt,
+ const SdpSctpmapAttributeList& sctpmaps) const {
+ ASSERT_TRUE(sctpmaps.HasEntry(search_pt));
+ auto attr = sctpmaps.GetEntry(search_pt);
+ ASSERT_EQ(expected_pt, search_pt);
+ ASSERT_EQ(expected_pt, attr.pt);
+ ASSERT_EQ(name, attr.name);
+ ASSERT_EQ(streams, attr.streams);
+ }
+
+ void CheckRtcpFb(const SdpRtcpFbAttributeList::Feedback& feedback,
+ const std::string& pt,
+ SdpRtcpFbAttributeList::Type type,
+ const std::string& first_parameter,
+ const std::string& extra = "") const {
+ ASSERT_EQ(pt, feedback.pt);
+ ASSERT_EQ(type, feedback.type);
+ ASSERT_EQ(first_parameter, feedback.parameter);
+ ASSERT_EQ(extra, feedback.extra);
+ }
+
+ void CheckDtmfFmtp(const std::string& expectedDtmfTones) const {
+ ASSERT_TRUE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kFmtpAttribute));
+ auto audio_format_params =
+ mSdp->GetMediaSection(0).GetAttributeList().GetFmtp().mFmtps;
+ ASSERT_EQ(2U, audio_format_params.size());
+
+ ASSERT_EQ("101", audio_format_params[1].format);
+ ASSERT_TRUE(!!audio_format_params[1].parameters);
+ const SdpFmtpAttributeList::TelephoneEventParameters* te_parameters =
+ static_cast<SdpFmtpAttributeList::TelephoneEventParameters*>(
+ audio_format_params[1].parameters.get());
+ ASSERT_NE(0U, te_parameters->dtmfTones.size());
+ ASSERT_EQ(expectedDtmfTones, te_parameters->dtmfTones);
+ }
+
+ void CheckSerialize(const std::string& expected,
+ const SdpAttribute& attr) const {
+ std::stringstream str;
+ attr.Serialize(str);
+ ASSERT_EQ(expected, str.str());
+ }
+
+ SipccSdpParser mParser;
+ mozilla::UniquePtr<Sdp> mSdp;
+}; // class NewSdpTest
+
+TEST_P(NewSdpTest, CreateDestroy) {
+}
+
+TEST_P(NewSdpTest, ParseEmpty) {
+ ParseSdp("", false);
+ ASSERT_FALSE(mSdp);
+ ASSERT_NE(0U, mParser.GetParseErrors().size())
+ << "Expected at least one parse error.";
+}
+
+const std::string kBadSdp = "This is SDPARTA!!!!";
+
+TEST_P(NewSdpTest, ParseGarbage) {
+ ParseSdp(kBadSdp, false);
+ ASSERT_FALSE(mSdp);
+ ASSERT_NE(0U, mParser.GetParseErrors().size())
+ << "Expected at least one parse error.";
+}
+
+TEST_P(NewSdpTest, ParseGarbageTwice) {
+ ParseSdp(kBadSdp, false);
+ ASSERT_FALSE(mSdp);
+ size_t errorCount = mParser.GetParseErrors().size();
+ ASSERT_NE(0U, errorCount)
+ << "Expected at least one parse error.";
+ ParseSdp(kBadSdp, false);
+ ASSERT_FALSE(mSdp);
+ ASSERT_EQ(errorCount, mParser.GetParseErrors().size())
+ << "Expected same error count for same SDP.";
+}
+
+TEST_P(NewSdpTest, ParseMinimal) {
+ ParseSdp(kVideoSdp);
+ ASSERT_EQ(0U, mParser.GetParseErrors().size()) <<
+ "Got parse errors: " << GetParseErrors();
+}
+
+TEST_P(NewSdpTest, CheckOriginGetUsername) {
+ ParseSdp(kVideoSdp);
+ ASSERT_EQ("-", mSdp->GetOrigin().GetUsername())
+ << "Wrong username in origin";
+}
+
+TEST_P(NewSdpTest, CheckOriginGetSessionId) {
+ ParseSdp(kVideoSdp);
+ ASSERT_EQ(4294967296U, mSdp->GetOrigin().GetSessionId())
+ << "Wrong session id in origin";
+}
+
+TEST_P(NewSdpTest, CheckOriginGetSessionVersion) {
+ ParseSdp(kVideoSdp);
+ ASSERT_EQ(2U , mSdp->GetOrigin().GetSessionVersion())
+ << "Wrong version in origin";
+}
+
+TEST_P(NewSdpTest, CheckOriginGetAddrType) {
+ ParseSdp(kVideoSdp);
+ ASSERT_EQ(sdp::kIPv4, mSdp->GetOrigin().GetAddrType())
+ << "Wrong address type in origin";
+}
+
+TEST_P(NewSdpTest, CheckOriginGetAddress) {
+ ParseSdp(kVideoSdp);
+ ASSERT_EQ("127.0.0.1" , mSdp->GetOrigin().GetAddress())
+ << "Wrong address in origin";
+}
+
+TEST_P(NewSdpTest, CheckGetMissingBandwidth) {
+ ParseSdp(kVideoSdp);
+ ASSERT_EQ(0U, mSdp->GetBandwidth("CT"))
+ << "Wrong bandwidth in session";
+}
+
+TEST_P(NewSdpTest, CheckGetBandwidth) {
+ ParseSdp("v=0" CRLF
+ "o=- 4294967296 2 IN IP4 127.0.0.1" CRLF
+ "s=SIP Call" CRLF
+ "c=IN IP4 198.51.100.7" CRLF
+ "b=CT:5000" CRLF
+ "b=FOOBAR:10" CRLF
+ "b=AS:4" CRLF
+ "t=0 0" CRLF
+ "m=video 56436 RTP/SAVPF 120" CRLF
+ "a=rtpmap:120 VP8/90000" CRLF
+ );
+ ASSERT_EQ(5000U, mSdp->GetBandwidth("CT"))
+ << "Wrong CT bandwidth in session";
+ ASSERT_EQ(0U, mSdp->GetBandwidth("FOOBAR"))
+ << "Wrong FOOBAR bandwidth in session";
+ ASSERT_EQ(4U, mSdp->GetBandwidth("AS"))
+ << "Wrong AS bandwidth in session";
+}
+
+TEST_P(NewSdpTest, CheckGetMediaSectionsCount) {
+ ParseSdp(kVideoSdp);
+ ASSERT_EQ(1U, mSdp->GetMediaSectionCount())
+ << "Wrong number of media sections";
+}
+
+TEST_P(NewSdpTest, CheckMediaSectionGetMediaType) {
+ ParseSdp(kVideoSdp);
+ ASSERT_EQ(SdpMediaSection::kVideo, mSdp->GetMediaSection(0).GetMediaType())
+ << "Wrong type for first media section";
+}
+
+TEST_P(NewSdpTest, CheckMediaSectionGetProtocol) {
+ ParseSdp(kVideoSdp);
+ ASSERT_EQ(SdpMediaSection::kRtpSavpf, mSdp->GetMediaSection(0).GetProtocol())
+ << "Wrong protocol for video";
+}
+
+TEST_P(NewSdpTest, CheckMediaSectionGetFormats) {
+ ParseSdp(kVideoSdp);
+ auto video_formats = mSdp->GetMediaSection(0).GetFormats();
+ ASSERT_EQ(1U, video_formats.size()) << "Wrong number of formats for video";
+ ASSERT_EQ("120", video_formats[0]);
+}
+
+TEST_P(NewSdpTest, CheckMediaSectionGetPort) {
+ ParseSdp(kVideoSdp);
+ ASSERT_EQ(56436U, mSdp->GetMediaSection(0).GetPort())
+ << "Wrong port number in media section";
+}
+
+TEST_P(NewSdpTest, CheckMediaSectionGetMissingPortCount) {
+ ParseSdp(kVideoSdp);
+ ASSERT_EQ(0U, mSdp->GetMediaSection(0).GetPortCount())
+ << "Wrong port count in media section";
+}
+
+TEST_P(NewSdpTest, CheckMediaSectionGetPortCount) {
+ ParseSdp(kVideoSdp +
+ "m=audio 12345/2 RTP/SAVPF 0" CRLF
+ "a=rtpmap:0 PCMU/8000" CRLF
+ );
+ ASSERT_EQ(2U, mSdp->GetMediaSectionCount())
+ << "Wrong number of media sections";
+ ASSERT_EQ(2U, mSdp->GetMediaSection(1).GetPortCount())
+ << "Wrong port count in media section";
+}
+
+TEST_P(NewSdpTest, CheckMediaSectionGetMissingBandwidth) {
+ ParseSdp(kVideoSdp);
+ ASSERT_EQ(0U, mSdp->GetMediaSection(0).GetBandwidth("CT"))
+ << "Wrong bandwidth in media section";
+}
+
+TEST_P(NewSdpTest, CheckMediaSectionGetBandwidth) {
+ ParseSdp("v=0\r\n"
+ "o=- 4294967296 2 IN IP4 127.0.0.1\r\n"
+ "c=IN IP4 198.51.100.7\r\n"
+ "t=0 0\r\n"
+ "m=video 56436 RTP/SAVPF 120\r\n"
+ "b=CT:1000\r\n"
+ "a=rtpmap:120 VP8/90000\r\n");
+ ASSERT_EQ(1000U, mSdp->GetMediaSection(0).GetBandwidth("CT"))
+ << "Wrong bandwidth in media section";
+}
+
+// Define a string that is 258 characters long. We use a long string here so
+// that we can test that we are able to parse and handle a string longer than
+// the default maximum length of 256 in sipcc.
+#define ID_A "1234567890abcdef"
+#define ID_B ID_A ID_A ID_A ID_A
+#define LONG_IDENTITY ID_B ID_B ID_B ID_B "xx"
+
+#define BASE64_DTLS_HELLO "FgEAAAAAAAAAAAAAagEAAF4AAAAAAAAAXgEARI11KHx3QB6Ky" \
+ "CKgoBj/kwjKrApkL8kiZLwIqBaJGT8AAAA2ADkAOAA1ABYAEwAKADMAMgAvAAcAZgAFAAQAYw" \
+ "BiAGEAFQASAAkAZQBkAGAAFAARAAgABgADAQA="
+
+// SDP from a basic A/V apprtc call FFX/FFX
+const std::string kBasicAudioVideoOffer =
+"v=0" CRLF
+"o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
+"s=SIP Call" CRLF
+"c=IN IP4 224.0.0.1/100/12" CRLF
+"t=0 0" CRLF
+"a=dtls-message:client " BASE64_DTLS_HELLO CRLF
+"a=ice-ufrag:4a799b2e" CRLF
+"a=ice-pwd:e4cc12a910f106a0a744719425510e17" CRLF
+"a=ice-lite" CRLF
+"a=ice-options:trickle foo" CRLF
+"a=msid-semantic:WMS stream streama" CRLF
+"a=msid-semantic:foo stream" CRLF
+"a=fingerprint:sha-256 DF:2E:AC:8A:FD:0A:8E:99:BF:5D:E8:3C:E7:FA:FB:08:3B:3C:54:1D:D7:D4:05:77:A0:72:9B:14:08:6D:0F:4C" CRLF
+"a=identity:" LONG_IDENTITY CRLF
+"a=group:BUNDLE first second" CRLF
+"a=group:BUNDLE third" CRLF
+"a=group:LS first third" CRLF
+"m=audio 9 RTP/SAVPF 109 9 0 8 101" CRLF
+"c=IN IP4 0.0.0.0" CRLF
+"a=mid:first" CRLF
+"a=rtpmap:109 opus/48000/2" CRLF
+"a=fmtp:109 maxplaybackrate=32000;stereo=1" CRLF
+"a=ptime:20" CRLF
+"a=maxptime:20" CRLF
+"a=rtpmap:9 G722/8000" CRLF
+"a=rtpmap:0 PCMU/8000" CRLF
+"a=rtpmap:8 PCMA/8000" CRLF
+"a=rtpmap:101 telephone-event/8000" CRLF
+"a=fmtp:101 0-15,66,32-34,67" CRLF
+"a=ice-ufrag:00000000" CRLF
+"a=ice-pwd:0000000000000000000000000000000" CRLF
+"a=sendonly" CRLF
+"a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level" CRLF
+"a=setup:actpass" CRLF
+"a=rtcp-mux" CRLF
+"a=msid:stream track" CRLF
+"a=candidate:0 1 UDP 2130379007 10.0.0.36 62453 typ host" CRLF
+"a=candidate:2 1 UDP 1694236671 24.6.134.204 62453 typ srflx raddr 10.0.0.36 rport 62453" CRLF
+"a=candidate:3 1 UDP 100401151 162.222.183.171 49761 typ relay raddr 162.222.183.171 rport 49761" CRLF
+"a=candidate:6 1 UDP 16515071 162.222.183.171 51858 typ relay raddr 162.222.183.171 rport 51858" CRLF
+"a=candidate:3 2 UDP 100401150 162.222.183.171 62454 typ relay raddr 162.222.183.171 rport 62454" CRLF
+"a=candidate:2 2 UDP 1694236670 24.6.134.204 55428 typ srflx raddr 10.0.0.36 rport 55428" CRLF
+"a=candidate:6 2 UDP 16515070 162.222.183.171 50340 typ relay raddr 162.222.183.171 rport 50340" CRLF
+"a=candidate:0 2 UDP 2130379006 10.0.0.36 55428 typ host" CRLF
+"a=rtcp:62454 IN IP4 162.222.183.171" CRLF
+"a=end-of-candidates" CRLF
+"a=ssrc:5150" CRLF
+"m=video 9 RTP/SAVPF 120 121 122 123" CRLF
+"c=IN IP6 ::1" CRLF
+"a=fingerprint:sha-1 DF:FA:FB:08:3B:3C:54:1D:D7:D4:05:77:A0:72:9B:14:08:6D:0F:4C:2E:AC:8A:FD:0A:8E:99:BF:5D:E8:3C:E7" CRLF
+"a=mid:second" CRLF
+"a=rtpmap:120 VP8/90000" CRLF
+"a=fmtp:120 max-fs=3600;max-fr=30" CRLF
+"a=rtpmap:121 VP9/90000" CRLF
+"a=fmtp:121 max-fs=3600;max-fr=30" CRLF
+"a=rtpmap:122 red/90000" CRLF
+"a=rtpmap:123 ulpfec/90000" CRLF
+"a=recvonly" CRLF
+"a=rtcp-fb:120 nack" CRLF
+"a=rtcp-fb:120 nack pli" CRLF
+"a=rtcp-fb:120 ccm fir" CRLF
+"a=rtcp-fb:121 nack" CRLF
+"a=rtcp-fb:121 nack pli" CRLF
+"a=rtcp-fb:121 ccm fir" CRLF
+"a=setup:active" CRLF
+"a=rtcp-mux" CRLF
+"a=msid:streama tracka" CRLF
+"a=msid:streamb trackb" CRLF
+"a=candidate:0 1 UDP 2130379007 10.0.0.36 59530 typ host" CRLF
+"a=candidate:0 2 UDP 2130379006 10.0.0.36 64378 typ host" CRLF
+"a=candidate:2 2 UDP 1694236670 24.6.134.204 64378 typ srflx raddr 10.0.0.36 rport 64378" CRLF
+"a=candidate:6 2 UDP 16515070 162.222.183.171 64941 typ relay raddr 162.222.183.171 rport 64941" CRLF
+"a=candidate:6 1 UDP 16515071 162.222.183.171 64800 typ relay raddr 162.222.183.171 rport 64800" CRLF
+"a=candidate:2 1 UDP 1694236671 24.6.134.204 59530 typ srflx raddr 10.0.0.36 rport 59530" CRLF
+"a=candidate:3 1 UDP 100401151 162.222.183.171 62935 typ relay raddr 162.222.183.171 rport 62935" CRLF
+"a=candidate:3 2 UDP 100401150 162.222.183.171 61026 typ relay raddr 162.222.183.171 rport 61026" CRLF
+"a=rtcp:61026" CRLF
+"a=end-of-candidates" CRLF
+"a=ssrc:1111 foo" CRLF
+"a=ssrc:1111 foo:bar" CRLF
+"a=imageattr:120 send * recv *" CRLF
+"a=imageattr:121 send [x=640,y=480] recv [x=640,y=480]" CRLF
+"a=simulcast:recv pt=120;121" CRLF
+"a=rid:bar recv pt=96;max-width=800;max-height=600" CRLF
+"m=audio 9 RTP/SAVPF 0" CRLF
+"a=mid:third" CRLF
+"a=rtpmap:0 PCMU/8000" CRLF
+"a=ice-lite" CRLF
+"a=ice-options:foo bar" CRLF
+"a=msid:noappdata" CRLF
+"a=bundle-only" CRLF;
+
+TEST_P(NewSdpTest, BasicAudioVideoSdpParse) {
+ ParseSdp(kBasicAudioVideoOffer);
+}
+
+TEST_P(NewSdpTest, CheckRemoveFmtp) {
+ ParseSdp(kBasicAudioVideoOffer);
+ ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+ ASSERT_EQ(3U, mSdp->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ SdpAttributeList& audioAttrList = mSdp->GetMediaSection(0).GetAttributeList();
+
+ ASSERT_TRUE(audioAttrList.HasAttribute(SdpAttribute::kFmtpAttribute));
+ ASSERT_EQ(2U, audioAttrList.GetFmtp().mFmtps.size());
+ ASSERT_TRUE(mSdp->GetMediaSection(0).FindFmtp("109"));
+ ASSERT_TRUE(mSdp->GetMediaSection(0).FindFmtp("101"));
+
+ mSdp->GetMediaSection(0).RemoveFmtp("101");
+
+ ASSERT_TRUE(audioAttrList.HasAttribute(SdpAttribute::kFmtpAttribute));
+ ASSERT_EQ(1U, audioAttrList.GetFmtp().mFmtps.size());
+ ASSERT_TRUE(mSdp->GetMediaSection(0).FindFmtp("109"));
+ ASSERT_FALSE(mSdp->GetMediaSection(0).FindFmtp("101"));
+
+ mSdp->GetMediaSection(0).RemoveFmtp("109");
+
+ ASSERT_TRUE(audioAttrList.HasAttribute(SdpAttribute::kFmtpAttribute));
+ ASSERT_EQ(0U, audioAttrList.GetFmtp().mFmtps.size());
+ ASSERT_FALSE(mSdp->GetMediaSection(0).FindFmtp("109"));
+ ASSERT_FALSE(mSdp->GetMediaSection(0).FindFmtp("101"));
+
+ // make sure we haven't disturbed the video fmtps
+ SdpAttributeList& videoAttrList = mSdp->GetMediaSection(1).GetAttributeList();
+ ASSERT_TRUE(videoAttrList.HasAttribute(SdpAttribute::kFmtpAttribute));
+ ASSERT_EQ(2U, videoAttrList.GetFmtp().mFmtps.size());
+ ASSERT_TRUE(mSdp->GetMediaSection(1).FindFmtp("120"));
+ ASSERT_TRUE(mSdp->GetMediaSection(1).FindFmtp("121"));
+}
+
+TEST_P(NewSdpTest, CheckIceUfrag) {
+ ParseSdp(kBasicAudioVideoOffer);
+ ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+ ASSERT_TRUE(mSdp->GetAttributeList().HasAttribute(
+ SdpAttribute::kIceUfragAttribute));
+ auto ice_ufrag = mSdp->GetAttributeList().GetIceUfrag();
+ ASSERT_EQ("4a799b2e", ice_ufrag) << "Wrong ice-ufrag value";
+
+ ice_ufrag = mSdp->GetMediaSection(0)
+ .GetAttributeList().GetIceUfrag();
+ ASSERT_EQ("00000000", ice_ufrag) << "ice-ufrag isn't overridden";
+
+ ice_ufrag = mSdp->GetMediaSection(1)
+ .GetAttributeList().GetIceUfrag();
+ ASSERT_EQ("4a799b2e", ice_ufrag) << "ice-ufrag isn't carried to m-section";
+}
+
+TEST_P(NewSdpTest, CheckIcePwd) {
+ ParseSdp(kBasicAudioVideoOffer);
+ ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+ ASSERT_TRUE(mSdp->GetAttributeList().HasAttribute(
+ SdpAttribute::kIcePwdAttribute));
+ auto ice_pwd = mSdp->GetAttributeList().GetIcePwd();
+ ASSERT_EQ("e4cc12a910f106a0a744719425510e17", ice_pwd) << "Wrong ice-pwd value";
+
+ ice_pwd = mSdp->GetMediaSection(0)
+ .GetAttributeList().GetIcePwd();
+ ASSERT_EQ("0000000000000000000000000000000", ice_pwd)
+ << "ice-pwd isn't overridden";
+
+ ice_pwd = mSdp->GetMediaSection(1)
+ .GetAttributeList().GetIcePwd();
+ ASSERT_EQ("e4cc12a910f106a0a744719425510e17", ice_pwd)
+ << "ice-pwd isn't carried to m-section";
+}
+
+TEST_P(NewSdpTest, CheckIceOptions) {
+ ParseSdp(kBasicAudioVideoOffer);
+ ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+ ASSERT_TRUE(mSdp->GetAttributeList().HasAttribute(
+ SdpAttribute::kIceOptionsAttribute));
+ auto ice_options = mSdp->GetAttributeList().GetIceOptions();
+ ASSERT_EQ(2U, ice_options.mValues.size()) << "Wrong ice-options size";
+ ASSERT_EQ("trickle", ice_options.mValues[0]) << "Wrong ice-options value";
+ ASSERT_EQ("foo", ice_options.mValues[1]) << "Wrong ice-options value";
+
+ ASSERT_TRUE(mSdp->GetMediaSection(2).GetAttributeList().HasAttribute(
+ SdpAttribute::kIceOptionsAttribute));
+ auto ice_options_media_level =
+ mSdp->GetMediaSection(2).GetAttributeList().GetIceOptions();
+ ASSERT_EQ(2U, ice_options_media_level.mValues.size()) << "Wrong ice-options size";
+ ASSERT_EQ("foo", ice_options_media_level.mValues[0]) << "Wrong ice-options value";
+ ASSERT_EQ("bar", ice_options_media_level.mValues[1]) << "Wrong ice-options value";
+}
+
+TEST_P(NewSdpTest, CheckFingerprint) {
+ ParseSdp(kBasicAudioVideoOffer);
+ ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+ ASSERT_TRUE(mSdp->GetAttributeList().HasAttribute(
+ SdpAttribute::kFingerprintAttribute));
+ auto fingerprints = mSdp->GetAttributeList().GetFingerprint();
+ ASSERT_EQ(1U, fingerprints.mFingerprints.size());
+ ASSERT_EQ(SdpFingerprintAttributeList::kSha256,
+ fingerprints.mFingerprints[0].hashFunc)
+ << "Wrong hash function";
+ ASSERT_EQ("DF:2E:AC:8A:FD:0A:8E:99:BF:5D:E8:3C:E7:FA:FB:08:"
+ "3B:3C:54:1D:D7:D4:05:77:A0:72:9B:14:08:6D:0F:4C",
+ SdpFingerprintAttributeList::FormatFingerprint(
+ fingerprints.mFingerprints[0].fingerprint))
+ << "Wrong fingerprint";
+ ASSERT_EQ(0xdfU, fingerprints.mFingerprints[0].fingerprint[0])
+ << "first fingerprint element is iffy";
+
+ ASSERT_EQ(3U, mSdp->GetMediaSectionCount());
+
+ // Fallback to session level
+ ASSERT_TRUE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kFingerprintAttribute));
+ fingerprints = mSdp->GetMediaSection(0).GetAttributeList().GetFingerprint();
+ ASSERT_EQ(1U, fingerprints.mFingerprints.size());
+ ASSERT_EQ(SdpFingerprintAttributeList::kSha256,
+ fingerprints.mFingerprints[0].hashFunc)
+ << "Wrong hash function";
+ ASSERT_EQ("DF:2E:AC:8A:FD:0A:8E:99:BF:5D:E8:3C:E7:FA:FB:08:"
+ "3B:3C:54:1D:D7:D4:05:77:A0:72:9B:14:08:6D:0F:4C",
+ SdpFingerprintAttributeList::FormatFingerprint(
+ fingerprints.mFingerprints[0].fingerprint))
+ << "Wrong fingerprint";
+ ASSERT_EQ(0xdfU, fingerprints.mFingerprints[0].fingerprint[0])
+ << "first fingerprint element is iffy";
+
+ // Media level
+ ASSERT_TRUE(mSdp->GetMediaSection(1).GetAttributeList().HasAttribute(
+ SdpAttribute::kFingerprintAttribute));
+ fingerprints = mSdp->GetMediaSection(1).GetAttributeList().GetFingerprint();
+ ASSERT_EQ(1U, fingerprints.mFingerprints.size());
+ ASSERT_EQ(SdpFingerprintAttributeList::kSha1,
+ fingerprints.mFingerprints[0].hashFunc)
+ << "Wrong hash function";
+ ASSERT_EQ("DF:FA:FB:08:3B:3C:54:1D:D7:D4:05:77:A0:72:9B:14:"
+ "08:6D:0F:4C:2E:AC:8A:FD:0A:8E:99:BF:5D:E8:3C:E7",
+ SdpFingerprintAttributeList::FormatFingerprint(
+ fingerprints.mFingerprints[0].fingerprint))
+ << "Wrong fingerprint";
+ ASSERT_EQ(0xdfU, fingerprints.mFingerprints[0].fingerprint[0])
+ << "first fingerprint element is iffy";
+}
+
+TEST_P(NewSdpTest, CheckIdentity) {
+ ParseSdp(kBasicAudioVideoOffer);
+ ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+ ASSERT_TRUE(mSdp->GetAttributeList().HasAttribute(
+ SdpAttribute::kIdentityAttribute));
+ auto identity = mSdp->GetAttributeList().GetIdentity();
+ ASSERT_EQ(LONG_IDENTITY, identity) << "Wrong identity assertion";
+}
+
+TEST_P(NewSdpTest, CheckDtlsMessage) {
+ ParseSdp(kBasicAudioVideoOffer);
+ ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+ ASSERT_TRUE(mSdp->GetAttributeList().HasAttribute(
+ SdpAttribute::kDtlsMessageAttribute));
+ auto dtls_message = mSdp->GetAttributeList().GetDtlsMessage();
+ ASSERT_EQ(SdpDtlsMessageAttribute::kClient, dtls_message.mRole)
+ << "Wrong dtls-message role";
+ ASSERT_EQ(BASE64_DTLS_HELLO, dtls_message.mValue)
+ << "Wrong dtls-message value";
+}
+
+TEST_P(NewSdpTest, CheckNumberOfMediaSections) {
+ ParseSdp(kBasicAudioVideoOffer);
+ ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+ ASSERT_EQ(3U, mSdp->GetMediaSectionCount()) << "Wrong number of media sections";
+}
+
+TEST_P(NewSdpTest, CheckMlines) {
+ ParseSdp(kBasicAudioVideoOffer);
+ ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+ ASSERT_EQ(3U, mSdp->GetMediaSectionCount()) << "Wrong number of media sections";
+ ASSERT_EQ(SdpMediaSection::kAudio, mSdp->GetMediaSection(0).GetMediaType())
+ << "Wrong type for first media section";
+ ASSERT_EQ(SdpMediaSection::kRtpSavpf,
+ mSdp->GetMediaSection(0).GetProtocol())
+ << "Wrong protocol for audio";
+ auto audio_formats = mSdp->GetMediaSection(0).GetFormats();
+ ASSERT_EQ(5U, audio_formats.size()) << "Wrong number of formats for audio";
+ ASSERT_EQ("109", audio_formats[0]);
+ ASSERT_EQ("9", audio_formats[1]);
+ ASSERT_EQ("0", audio_formats[2]);
+ ASSERT_EQ("8", audio_formats[3]);
+ ASSERT_EQ("101", audio_formats[4]);
+
+ ASSERT_EQ(SdpMediaSection::kVideo, mSdp->GetMediaSection(1).GetMediaType())
+ << "Wrong type for second media section";
+ ASSERT_EQ(SdpMediaSection::kRtpSavpf,
+ mSdp->GetMediaSection(1).GetProtocol())
+ << "Wrong protocol for video";
+ auto video_formats = mSdp->GetMediaSection(1).GetFormats();
+ ASSERT_EQ(4U, video_formats.size()) << "Wrong number of formats for video";
+ ASSERT_EQ("120", video_formats[0]);
+ ASSERT_EQ("121", video_formats[1]);
+ ASSERT_EQ("122", video_formats[2]);
+ ASSERT_EQ("123", video_formats[3]);
+
+ ASSERT_EQ(SdpMediaSection::kAudio, mSdp->GetMediaSection(2).GetMediaType())
+ << "Wrong type for third media section";
+}
+
+TEST_P(NewSdpTest, CheckSetup) {
+ ParseSdp(kBasicAudioVideoOffer);
+ ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+ ASSERT_EQ(3U, mSdp->GetMediaSectionCount()) << "Wrong number of media sections";
+
+ ASSERT_TRUE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kSetupAttribute));
+ ASSERT_EQ(SdpSetupAttribute::kActpass,
+ mSdp->GetMediaSection(0).GetAttributeList().GetSetup().mRole);
+ ASSERT_TRUE(mSdp->GetMediaSection(1).GetAttributeList().HasAttribute(
+ SdpAttribute::kSetupAttribute));
+ ASSERT_EQ(SdpSetupAttribute::kActive,
+ mSdp->GetMediaSection(1).GetAttributeList().GetSetup().mRole);
+ ASSERT_FALSE(mSdp->GetMediaSection(2).GetAttributeList().HasAttribute(
+ SdpAttribute::kSetupAttribute));
+}
+
+TEST_P(NewSdpTest, CheckSsrc)
+{
+ ParseSdp(kBasicAudioVideoOffer);
+ ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+ ASSERT_EQ(3U, mSdp->GetMediaSectionCount()) << "Wrong number of media sections";
+
+ ASSERT_TRUE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kSsrcAttribute));
+ auto ssrcs = mSdp->GetMediaSection(0).GetAttributeList().GetSsrc().mSsrcs;
+ ASSERT_EQ(1U, ssrcs.size());
+ ASSERT_EQ(5150U, ssrcs[0].ssrc);
+ ASSERT_EQ("", ssrcs[0].attribute);
+
+ ASSERT_TRUE(mSdp->GetMediaSection(1).GetAttributeList().HasAttribute(
+ SdpAttribute::kSsrcAttribute));
+ ssrcs = mSdp->GetMediaSection(1).GetAttributeList().GetSsrc().mSsrcs;
+ ASSERT_EQ(2U, ssrcs.size());
+ ASSERT_EQ(1111U, ssrcs[0].ssrc);
+ ASSERT_EQ("foo", ssrcs[0].attribute);
+ ASSERT_EQ(1111U, ssrcs[1].ssrc);
+ ASSERT_EQ("foo:bar", ssrcs[1].attribute);
+}
+
+TEST_P(NewSdpTest, CheckRtpmap) {
+ ParseSdp(kBasicAudioVideoOffer);
+ ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+ ASSERT_EQ(3U, mSdp->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ const SdpMediaSection& audiosec = mSdp->GetMediaSection(0);
+ const SdpRtpmapAttributeList& rtpmap = audiosec.GetAttributeList().GetRtpmap();
+ ASSERT_EQ(5U, rtpmap.mRtpmaps.size())
+ << "Wrong number of rtpmap attributes for audio";
+
+ // Need to know name of type
+ CheckRtpmap("109",
+ SdpRtpmapAttributeList::kOpus,
+ "opus",
+ 48000,
+ 2,
+ audiosec.GetFormats()[0],
+ rtpmap);
+
+ CheckRtpmap("9",
+ SdpRtpmapAttributeList::kG722,
+ "G722",
+ 8000,
+ 1,
+ audiosec.GetFormats()[1],
+ rtpmap);
+
+ CheckRtpmap("0",
+ SdpRtpmapAttributeList::kPCMU,
+ "PCMU",
+ 8000,
+ 1,
+ audiosec.GetFormats()[2],
+ rtpmap);
+
+ CheckRtpmap("8",
+ SdpRtpmapAttributeList::kPCMA,
+ "PCMA",
+ 8000,
+ 1,
+ audiosec.GetFormats()[3],
+ rtpmap);
+
+ CheckRtpmap("101",
+ SdpRtpmapAttributeList::kTelephoneEvent,
+ "telephone-event",
+ 8000,
+ 1,
+ audiosec.GetFormats()[4],
+ rtpmap);
+
+ const SdpMediaSection& videosec = mSdp->GetMediaSection(1);
+ const SdpRtpmapAttributeList videoRtpmap =
+ videosec.GetAttributeList().GetRtpmap();
+ ASSERT_EQ(4U, videoRtpmap.mRtpmaps.size())
+ << "Wrong number of rtpmap attributes for video";
+
+ CheckRtpmap("120",
+ SdpRtpmapAttributeList::kVP8,
+ "VP8",
+ 90000,
+ 0,
+ videosec.GetFormats()[0],
+ videoRtpmap);
+
+ CheckRtpmap("121",
+ SdpRtpmapAttributeList::kVP9,
+ "VP9",
+ 90000,
+ 0,
+ videosec.GetFormats()[1],
+ videoRtpmap);
+
+ CheckRtpmap("122",
+ SdpRtpmapAttributeList::kRed,
+ "red",
+ 90000,
+ 0,
+ videosec.GetFormats()[2],
+ videoRtpmap);
+
+ CheckRtpmap("123",
+ SdpRtpmapAttributeList::kUlpfec,
+ "ulpfec",
+ 90000,
+ 0,
+ videosec.GetFormats()[3],
+ videoRtpmap);
+}
+
+static const std::string kAudioWithTelephoneEvent =
+ "v=0" CRLF
+ "o=- 4294967296 2 IN IP4 127.0.0.1" CRLF
+ "s=SIP Call" CRLF
+ "c=IN IP4 198.51.100.7" CRLF
+ "t=0 0" CRLF
+ "m=audio 9 RTP/SAVPF 109 9 0 8 101" CRLF
+ "c=IN IP4 0.0.0.0" CRLF
+ "a=mid:first" CRLF
+ "a=rtpmap:109 opus/48000/2" CRLF
+ "a=fmtp:109 maxplaybackrate=32000;stereo=1" CRLF
+ "a=ptime:20" CRLF
+ "a=maxptime:20" CRLF
+ "a=rtpmap:9 G722/8000" CRLF
+ "a=rtpmap:0 PCMU/8000" CRLF
+ "a=rtpmap:8 PCMA/8000" CRLF
+ "a=rtpmap:101 telephone-event/8000" CRLF;
+
+TEST_P(NewSdpTest, CheckTelephoneEventNoFmtp) {
+ ParseSdp(kAudioWithTelephoneEvent);
+ ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+ ASSERT_EQ(1U, mSdp->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ ASSERT_TRUE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kFmtpAttribute));
+ auto audio_format_params =
+ mSdp->GetMediaSection(0).GetAttributeList().GetFmtp().mFmtps;
+ ASSERT_EQ(1U, audio_format_params.size());
+
+ // make sure we don't get a fmtp for codec 101
+ for (size_t i = 0; i < audio_format_params.size(); ++i) {
+ ASSERT_NE("101", audio_format_params[i].format);
+ }
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventWithDefaultEvents) {
+ ParseSdp(kAudioWithTelephoneEvent
+ + "a=fmtp:101 0-15" CRLF);
+ ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+ ASSERT_EQ(1U, mSdp->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ CheckDtmfFmtp("0-15");
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventWithBadCharacter) {
+ ParseSdp(kAudioWithTelephoneEvent
+ + "a=fmtp:101 0-5." CRLF);
+ ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+ ASSERT_EQ(1U, mSdp->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ CheckDtmfFmtp("0-15");
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventIncludingCommas) {
+ ParseSdp(kAudioWithTelephoneEvent
+ + "a=fmtp:101 0-15,66,67" CRLF);
+ ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+ ASSERT_EQ(1U, mSdp->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ CheckDtmfFmtp("0-15,66,67");
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventComplexEvents) {
+ ParseSdp(kAudioWithTelephoneEvent
+ + "a=fmtp:101 0,1,2-4,5-15,66,67" CRLF);
+ ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+ ASSERT_EQ(1U, mSdp->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ CheckDtmfFmtp("0,1,2-4,5-15,66,67");
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventNoHyphen) {
+ ParseSdp(kAudioWithTelephoneEvent
+ + "a=fmtp:101 5,6,7" CRLF);
+ ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+ ASSERT_EQ(1U, mSdp->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ CheckDtmfFmtp("5,6,7");
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventOnlyZero) {
+ ParseSdp(kAudioWithTelephoneEvent
+ + "a=fmtp:101 0" CRLF);
+ ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+ ASSERT_EQ(1U, mSdp->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ CheckDtmfFmtp("0");
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventOnlyOne) {
+ ParseSdp(kAudioWithTelephoneEvent
+ + "a=fmtp:101 1" CRLF);
+ ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+ ASSERT_EQ(1U, mSdp->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ CheckDtmfFmtp("1");
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventBadThreeDigit) {
+ ParseSdp(kAudioWithTelephoneEvent
+ + "a=fmtp:101 123" CRLF);
+ ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+ ASSERT_EQ(1U, mSdp->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ // check for the default dtmf tones
+ CheckDtmfFmtp("0-15");
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventBadThreeDigitWithHyphen) {
+ ParseSdp(kAudioWithTelephoneEvent
+ + "a=fmtp:101 0-123" CRLF);
+ ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+ ASSERT_EQ(1U, mSdp->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ // check for the default dtmf tones
+ CheckDtmfFmtp("0-15");
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventBadLeadingHyphen) {
+ ParseSdp(kAudioWithTelephoneEvent
+ + "a=fmtp:101 -12" CRLF);
+ ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+ ASSERT_EQ(1U, mSdp->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ // check for the default dtmf tones
+ CheckDtmfFmtp("0-15");
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventBadTrailingHyphen) {
+ ParseSdp(kAudioWithTelephoneEvent
+ + "a=fmtp:101 12-" CRLF, false);
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventBadTrailingHyphenInMiddle) {
+ ParseSdp(kAudioWithTelephoneEvent
+ + "a=fmtp:101 1,12-,4" CRLF, false);
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventBadLeadingComma) {
+ ParseSdp(kAudioWithTelephoneEvent
+ + "a=fmtp:101 ,2,3" CRLF);
+ ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+ ASSERT_EQ(1U, mSdp->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ // check for the default dtmf tones
+ CheckDtmfFmtp("0-15");
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventBadMultipleLeadingComma) {
+ ParseSdp(kAudioWithTelephoneEvent
+ + "a=fmtp:101 ,,,2,3" CRLF);
+ ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+ ASSERT_EQ(1U, mSdp->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ // check for the default dtmf tones
+ CheckDtmfFmtp("0-15");
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventBadConsecutiveCommas) {
+ ParseSdp(kAudioWithTelephoneEvent
+ + "a=fmtp:101 1,,,,,,,,3" CRLF);
+ ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+ ASSERT_EQ(1U, mSdp->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ // check for the default dtmf tones
+ CheckDtmfFmtp("0-15");
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventBadTrailingComma) {
+ ParseSdp(kAudioWithTelephoneEvent
+ + "a=fmtp:101 1,2,3," CRLF);
+ ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+ ASSERT_EQ(1U, mSdp->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ // check for the default dtmf tones
+ CheckDtmfFmtp("0-15");
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventBadTwoHyphens) {
+ ParseSdp(kAudioWithTelephoneEvent
+ + "a=fmtp:101 1-2-3" CRLF);
+ ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+ ASSERT_EQ(1U, mSdp->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ // check for the default dtmf tones
+ CheckDtmfFmtp("0-15");
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventBadSixDigit) {
+ ParseSdp(kAudioWithTelephoneEvent
+ + "a=fmtp:101 112233" CRLF);
+ ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+ ASSERT_EQ(1U, mSdp->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ // check for the default dtmf tones
+ CheckDtmfFmtp("0-15");
+}
+
+TEST_P(NewSdpTest, CheckTelephoneEventBadRangeReversed) {
+ ParseSdp(kAudioWithTelephoneEvent
+ + "a=fmtp:101 33-2" CRLF);
+ ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+ ASSERT_EQ(1U, mSdp->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ // check for the default dtmf tones
+ CheckDtmfFmtp("0-15");
+}
+
+static const std::string kVideoWithRedAndUlpfecSdp =
+ "v=0" CRLF
+ "o=- 4294967296 2 IN IP4 127.0.0.1" CRLF
+ "s=SIP Call" CRLF
+ "c=IN IP4 198.51.100.7" CRLF
+ "t=0 0" CRLF
+ "m=video 9 RTP/SAVPF 97 120 121 122 123" CRLF
+ "c=IN IP6 ::1" CRLF
+ "a=fingerprint:sha-1 DF:FA:FB:08:3B:3C:54:1D:D7:D4:05:77:A0:72:9B:14:08:6D:0F:4C:2E:AC:8A:FD:0A:8E:99:BF:5D:E8:3C:E7" CRLF
+ "a=rtpmap:97 H264/90000" CRLF
+ "a=fmtp:97 profile-level-id=42a01e" CRLF
+ "a=rtpmap:120 VP8/90000" CRLF
+ "a=fmtp:120 max-fs=3600;max-fr=30" CRLF
+ "a=rtpmap:121 VP9/90000" CRLF
+ "a=fmtp:121 max-fs=3600;max-fr=30" CRLF
+ "a=rtpmap:122 red/90000" CRLF
+ "a=rtpmap:123 ulpfec/90000" CRLF;
+
+TEST_P(NewSdpTest, CheckRedNoFmtp) {
+ ParseSdp(kVideoWithRedAndUlpfecSdp);
+ ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+ ASSERT_EQ(1U, mSdp->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ ASSERT_TRUE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kFmtpAttribute));
+ auto video_format_params =
+ mSdp->GetMediaSection(0).GetAttributeList().GetFmtp().mFmtps;
+ ASSERT_EQ(3U, video_format_params.size());
+
+ // make sure we don't get a fmtp for codec 122
+ for (size_t i = 0; i < video_format_params.size(); ++i) {
+ ASSERT_NE("122", video_format_params[i].format);
+ }
+}
+
+TEST_P(NewSdpTest, CheckRedFmtpWith2Codecs) {
+ ParseSdp(kVideoWithRedAndUlpfecSdp + "a=fmtp:122 120/121" CRLF);
+ ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+ ASSERT_EQ(1U, mSdp->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ ASSERT_TRUE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kFmtpAttribute));
+ auto video_format_params =
+ mSdp->GetMediaSection(0).GetAttributeList().GetFmtp().mFmtps;
+ ASSERT_EQ(4U, video_format_params.size());
+
+ ASSERT_EQ("122", video_format_params[3].format);
+ ASSERT_TRUE(!!video_format_params[3].parameters);
+ ASSERT_EQ(SdpRtpmapAttributeList::kRed,
+ video_format_params[3].parameters->codec_type);
+ const SdpFmtpAttributeList::RedParameters* red_parameters(
+ static_cast<SdpFmtpAttributeList::RedParameters*>(
+ video_format_params[3].parameters.get()));
+ ASSERT_EQ(2U, red_parameters->encodings.size());
+ ASSERT_EQ(120U, red_parameters->encodings[0]);
+ ASSERT_EQ(121U, red_parameters->encodings[1]);
+}
+
+TEST_P(NewSdpTest, CheckRedFmtpWith3Codecs) {
+ ParseSdp(kVideoWithRedAndUlpfecSdp + "a=fmtp:122 120/121/123" CRLF);
+ ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+ ASSERT_EQ(1U, mSdp->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ ASSERT_TRUE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kFmtpAttribute));
+ auto video_format_params =
+ mSdp->GetMediaSection(0).GetAttributeList().GetFmtp().mFmtps;
+ ASSERT_EQ(4U, video_format_params.size());
+
+ ASSERT_EQ("122", video_format_params[3].format);
+ ASSERT_TRUE(!!video_format_params[3].parameters);
+ ASSERT_EQ(SdpRtpmapAttributeList::kRed,
+ video_format_params[3].parameters->codec_type);
+ const SdpFmtpAttributeList::RedParameters* red_parameters(
+ static_cast<SdpFmtpAttributeList::RedParameters*>(
+ video_format_params[3].parameters.get()));
+ ASSERT_EQ(3U, red_parameters->encodings.size());
+ ASSERT_EQ(120U, red_parameters->encodings[0]);
+ ASSERT_EQ(121U, red_parameters->encodings[1]);
+ ASSERT_EQ(123U, red_parameters->encodings[2]);
+}
+
+const std::string kH264AudioVideoOffer =
+"v=0" CRLF
+"o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
+"s=SIP Call" CRLF
+"c=IN IP4 224.0.0.1/100/12" CRLF
+"t=0 0" CRLF
+"a=ice-ufrag:4a799b2e" CRLF
+"a=ice-pwd:e4cc12a910f106a0a744719425510e17" CRLF
+"a=ice-lite" CRLF
+"a=msid-semantic:WMS stream streama" CRLF
+"a=fingerprint:sha-256 DF:2E:AC:8A:FD:0A:8E:99:BF:5D:E8:3C:E7:FA:FB:08:3B:3C:54:1D:D7:D4:05:77:A0:72:9B:14:08:6D:0F:4C" CRLF
+"a=group:BUNDLE first second" CRLF
+"a=group:BUNDLE third" CRLF
+"a=group:LS first third" CRLF
+"m=audio 9 RTP/SAVPF 109 9 0 8 101" CRLF
+"c=IN IP4 0.0.0.0" CRLF
+"a=mid:first" CRLF
+"a=rtpmap:109 opus/48000/2" CRLF
+"a=ptime:20" CRLF
+"a=maxptime:20" CRLF
+"a=rtpmap:9 G722/8000" CRLF
+"a=rtpmap:0 PCMU/8000" CRLF
+"a=rtpmap:8 PCMA/8000" CRLF
+"a=rtpmap:101 telephone-event/8000" CRLF
+"a=fmtp:109 maxplaybackrate=32000;stereo=1;useinbandfec=1" CRLF
+"a=fmtp:101 0-15,66,32-34,67" CRLF
+"a=ice-ufrag:00000000" CRLF
+"a=ice-pwd:0000000000000000000000000000000" CRLF
+"a=sendonly" CRLF
+"a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level" CRLF
+"a=setup:actpass" CRLF
+"a=rtcp-mux" CRLF
+"a=msid:stream track" CRLF
+"a=candidate:0 1 UDP 2130379007 10.0.0.36 62453 typ host" CRLF
+"a=candidate:2 1 UDP 1694236671 24.6.134.204 62453 typ srflx raddr 10.0.0.36 rport 62453" CRLF
+"a=candidate:3 1 UDP 100401151 162.222.183.171 49761 typ relay raddr 162.222.183.171 rport 49761" CRLF
+"a=candidate:6 1 UDP 16515071 162.222.183.171 51858 typ relay raddr 162.222.183.171 rport 51858" CRLF
+"a=candidate:3 2 UDP 100401150 162.222.183.171 62454 typ relay raddr 162.222.183.171 rport 62454" CRLF
+"a=candidate:2 2 UDP 1694236670 24.6.134.204 55428 typ srflx raddr 10.0.0.36 rport 55428" CRLF
+"a=candidate:6 2 UDP 16515070 162.222.183.171 50340 typ relay raddr 162.222.183.171 rport 50340" CRLF
+"a=candidate:0 2 UDP 2130379006 10.0.0.36 55428 typ host" CRLF
+"m=video 9 RTP/SAVPF 97 98 120" CRLF
+"c=IN IP6 ::1" CRLF
+"a=mid:second" CRLF
+"a=rtpmap:97 H264/90000" CRLF
+"a=fmtp:97 profile-level-id=42a01e" CRLF
+"a=rtpmap:98 H264/90000" CRLF
+"a=fmtp:98 PROFILE=0;LEVEL=0;profile-level-id=42a00d;packetization-mode=1;level-asymmetry-allowed=1;max-mbps=42000;max-fs=1400;max-cpb=1000;max-dpb=1000;max-br=180000;parameter-add=1;usedtx=0;stereo=0;useinbandfec=0;cbr=0" CRLF
+"a=rtpmap:120 VP8/90000" CRLF
+"a=fmtp:120 max-fs=3601;max-fr=31" CRLF
+"a=recvonly" CRLF
+"a=setup:active" CRLF
+"a=rtcp-mux" CRLF
+"a=msid:streama tracka" CRLF
+"a=msid:streamb trackb" CRLF
+"a=candidate:0 1 UDP 2130379007 10.0.0.36 59530 typ host" CRLF
+"a=candidate:0 2 UDP 2130379006 10.0.0.36 64378 typ host" CRLF
+"a=candidate:2 2 UDP 1694236670 24.6.134.204 64378 typ srflx raddr 10.0.0.36 rport 64378" CRLF
+"a=candidate:6 2 UDP 16515070 162.222.183.171 64941 typ relay raddr 162.222.183.171 rport 64941" CRLF
+"a=candidate:6 1 UDP 16515071 162.222.183.171 64800 typ relay raddr 162.222.183.171 rport 64800" CRLF
+"a=candidate:2 1 UDP 1694236671 24.6.134.204 59530 typ srflx raddr 10.0.0.36 rport 59530" CRLF
+"a=candidate:3 1 UDP 100401151 162.222.183.171 62935 typ relay raddr 162.222.183.171 rport 62935" CRLF
+"a=candidate:3 2 UDP 100401150 162.222.183.171 61026 typ relay raddr 162.222.183.171 rport 61026" CRLF
+"m=audio 9 RTP/SAVPF 0" CRLF
+"a=mid:third" CRLF
+"a=rtpmap:0 PCMU/8000" CRLF
+"a=ice-lite" CRLF
+"a=msid:noappdata" CRLF;
+
+TEST_P(NewSdpTest, CheckFormatParameters) {
+ ParseSdp(kH264AudioVideoOffer);
+ ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+ ASSERT_EQ(3U, mSdp->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ ASSERT_TRUE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kFmtpAttribute));
+ auto audio_format_params =
+ mSdp->GetMediaSection(0).GetAttributeList().GetFmtp().mFmtps;
+ ASSERT_EQ(2U, audio_format_params.size());
+ ASSERT_EQ("109", audio_format_params[0].format);
+ ASSERT_TRUE(!!audio_format_params[0].parameters);
+ const SdpFmtpAttributeList::OpusParameters* opus_parameters =
+ static_cast<SdpFmtpAttributeList::OpusParameters*>(
+ audio_format_params[0].parameters.get());
+ ASSERT_EQ(32000U, opus_parameters->maxplaybackrate);
+ ASSERT_EQ(1U, opus_parameters->stereo);
+ ASSERT_EQ(1U, opus_parameters->useInBandFec);
+ ASSERT_EQ("101", audio_format_params[1].format);
+ ASSERT_TRUE(!!audio_format_params[1].parameters);
+ const SdpFmtpAttributeList::TelephoneEventParameters* te_parameters =
+ static_cast<SdpFmtpAttributeList::TelephoneEventParameters*>(
+ audio_format_params[1].parameters.get());
+ ASSERT_NE(0U, te_parameters->dtmfTones.size());
+ ASSERT_EQ("0-15,66,32-34,67", te_parameters->dtmfTones);
+
+ ASSERT_TRUE(mSdp->GetMediaSection(1).GetAttributeList().HasAttribute(
+ SdpAttribute::kFmtpAttribute));
+ auto video_format_params =
+ mSdp->GetMediaSection(1).GetAttributeList().GetFmtp().mFmtps;
+ ASSERT_EQ(3U, video_format_params.size());
+ ASSERT_EQ("97", video_format_params[0].format);
+ ASSERT_TRUE(!!video_format_params[0].parameters);
+ ASSERT_EQ(SdpRtpmapAttributeList::kH264,
+ video_format_params[0].parameters->codec_type);
+ const SdpFmtpAttributeList::H264Parameters *h264_parameters(
+ static_cast<SdpFmtpAttributeList::H264Parameters*>(
+ video_format_params[0].parameters.get()));
+ ASSERT_EQ((uint32_t)0x42a01e, h264_parameters->profile_level_id);
+ ASSERT_EQ(0U, h264_parameters->packetization_mode);
+ ASSERT_FALSE(static_cast<bool>(h264_parameters->level_asymmetry_allowed));
+ ASSERT_EQ(0U, h264_parameters->max_mbps);
+ ASSERT_EQ(0U, h264_parameters->max_fs);
+ ASSERT_EQ(0U, h264_parameters->max_cpb);
+ ASSERT_EQ(0U, h264_parameters->max_dpb);
+ ASSERT_EQ(0U, h264_parameters->max_br);
+
+ ASSERT_EQ("98", video_format_params[1].format);
+ ASSERT_TRUE(!!video_format_params[1].parameters);
+ ASSERT_EQ(SdpRtpmapAttributeList::kH264,
+ video_format_params[1].parameters->codec_type);
+ h264_parameters =
+ static_cast<SdpFmtpAttributeList::H264Parameters*>(
+ video_format_params[1].parameters.get());
+ ASSERT_EQ((uint32_t)0x42a00d, h264_parameters->profile_level_id);
+ ASSERT_EQ(1U, h264_parameters->packetization_mode);
+ ASSERT_TRUE(static_cast<bool>(h264_parameters->level_asymmetry_allowed));
+ ASSERT_EQ(42000U, h264_parameters->max_mbps);
+ ASSERT_EQ(1400U, h264_parameters->max_fs);
+ ASSERT_EQ(1000U, h264_parameters->max_cpb);
+ ASSERT_EQ(1000U, h264_parameters->max_dpb);
+ ASSERT_EQ(180000U, h264_parameters->max_br);
+
+ ASSERT_EQ("120", video_format_params[2].format);
+ ASSERT_TRUE(!!video_format_params[2].parameters);
+ ASSERT_EQ(SdpRtpmapAttributeList::kVP8,
+ video_format_params[2].parameters->codec_type);
+ const SdpFmtpAttributeList::VP8Parameters *vp8_parameters =
+ static_cast<SdpFmtpAttributeList::VP8Parameters*>(
+ video_format_params[2].parameters.get());
+ ASSERT_EQ(3601U, vp8_parameters->max_fs);
+ ASSERT_EQ(31U, vp8_parameters->max_fr);
+
+ ASSERT_FALSE(mSdp->GetMediaSection(2).GetAttributeList().HasAttribute(
+ SdpAttribute::kFmtpAttribute));
+}
+
+TEST_P(NewSdpTest, CheckPtime) {
+ ParseSdp(kBasicAudioVideoOffer);
+ ASSERT_EQ(20U, mSdp->GetMediaSection(0).GetAttributeList().GetPtime());
+ ASSERT_FALSE(mSdp->GetMediaSection(1).GetAttributeList().HasAttribute(
+ SdpAttribute::kPtimeAttribute));
+}
+
+TEST_P(NewSdpTest, CheckFlags) {
+ ParseSdp(kBasicAudioVideoOffer);
+ ASSERT_TRUE(mSdp->GetAttributeList().HasAttribute(
+ SdpAttribute::kIceLiteAttribute));
+ ASSERT_FALSE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kIceLiteAttribute));
+ ASSERT_FALSE(mSdp->GetMediaSection(1).GetAttributeList().HasAttribute(
+ SdpAttribute::kIceLiteAttribute));
+ ASSERT_FALSE(mSdp->GetMediaSection(2).GetAttributeList().HasAttribute(
+ SdpAttribute::kIceLiteAttribute));
+
+ ASSERT_TRUE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kRtcpMuxAttribute));
+ ASSERT_FALSE(mSdp->GetMediaSection(2).GetAttributeList().HasAttribute(
+ SdpAttribute::kRtcpMuxAttribute));
+
+ ASSERT_FALSE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kBundleOnlyAttribute));
+ ASSERT_TRUE(mSdp->GetMediaSection(2).GetAttributeList().HasAttribute(
+ SdpAttribute::kBundleOnlyAttribute));
+
+ ASSERT_TRUE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kEndOfCandidatesAttribute));
+ ASSERT_TRUE(mSdp->GetMediaSection(1).GetAttributeList().HasAttribute(
+ SdpAttribute::kEndOfCandidatesAttribute));
+ ASSERT_FALSE(mSdp->GetMediaSection(2).GetAttributeList().HasAttribute(
+ SdpAttribute::kEndOfCandidatesAttribute));
+}
+
+TEST_P(NewSdpTest, CheckConnectionLines) {
+ ParseSdp(kBasicAudioVideoOffer);
+ ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+ ASSERT_EQ(3U, mSdp->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ const SdpConnection& conn1 = mSdp->GetMediaSection(0).GetConnection();
+ ASSERT_EQ(sdp::kIPv4, conn1.GetAddrType());
+ ASSERT_EQ("0.0.0.0", conn1.GetAddress());
+ ASSERT_EQ(0U, conn1.GetTtl());
+ ASSERT_EQ(0U, conn1.GetCount());
+
+ const SdpConnection& conn2 = mSdp->GetMediaSection(1).GetConnection();
+ ASSERT_EQ(sdp::kIPv6, conn2.GetAddrType());
+ ASSERT_EQ("::1", conn2.GetAddress());
+ ASSERT_EQ(0U, conn2.GetTtl());
+ ASSERT_EQ(0U, conn2.GetCount());
+
+ // tests that we can fall through to session level as appropriate
+ const SdpConnection& conn3 = mSdp->GetMediaSection(2).GetConnection();
+ ASSERT_EQ(sdp::kIPv4, conn3.GetAddrType());
+ ASSERT_EQ("224.0.0.1", conn3.GetAddress());
+ ASSERT_EQ(100U, conn3.GetTtl());
+ ASSERT_EQ(12U, conn3.GetCount());
+}
+
+TEST_P(NewSdpTest, CheckDirections) {
+ ParseSdp(kBasicAudioVideoOffer);
+
+ ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+ ASSERT_EQ(SdpDirectionAttribute::kSendonly,
+ mSdp->GetMediaSection(0).GetAttributeList().GetDirection());
+ ASSERT_EQ(SdpDirectionAttribute::kRecvonly,
+ mSdp->GetMediaSection(1).GetAttributeList().GetDirection());
+ ASSERT_EQ(SdpDirectionAttribute::kSendrecv,
+ mSdp->GetMediaSection(2).GetAttributeList().GetDirection());
+}
+
+TEST_P(NewSdpTest, CheckCandidates) {
+ ParseSdp(kBasicAudioVideoOffer);
+ ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+ ASSERT_EQ(3U, mSdp->GetMediaSectionCount()) << "Wrong number of media sections";
+
+ ASSERT_TRUE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kCandidateAttribute));
+ auto audio_candidates =
+ mSdp->GetMediaSection(0).GetAttributeList().GetCandidate();
+ ASSERT_EQ(8U, audio_candidates.size());
+ ASSERT_EQ("0 1 UDP 2130379007 10.0.0.36 62453 typ host", audio_candidates[0]);
+ ASSERT_EQ("2 1 UDP 1694236671 24.6.134.204 62453 typ srflx raddr 10.0.0.36 rport 62453", audio_candidates[1]);
+ ASSERT_EQ("3 1 UDP 100401151 162.222.183.171 49761 typ relay raddr 162.222.183.171 rport 49761", audio_candidates[2]);
+ ASSERT_EQ("6 1 UDP 16515071 162.222.183.171 51858 typ relay raddr 162.222.183.171 rport 51858", audio_candidates[3]);
+ ASSERT_EQ("3 2 UDP 100401150 162.222.183.171 62454 typ relay raddr 162.222.183.171 rport 62454", audio_candidates[4]);
+ ASSERT_EQ("2 2 UDP 1694236670 24.6.134.204 55428 typ srflx raddr 10.0.0.36 rport 55428", audio_candidates[5]);
+ ASSERT_EQ("6 2 UDP 16515070 162.222.183.171 50340 typ relay raddr 162.222.183.171 rport 50340", audio_candidates[6]);
+ ASSERT_EQ("0 2 UDP 2130379006 10.0.0.36 55428 typ host", audio_candidates[7]);
+
+ ASSERT_TRUE(mSdp->GetMediaSection(1).GetAttributeList().HasAttribute(
+ SdpAttribute::kCandidateAttribute));
+ auto video_candidates =
+ mSdp->GetMediaSection(1).GetAttributeList().GetCandidate();
+ ASSERT_EQ(8U, video_candidates.size());
+ ASSERT_EQ("0 1 UDP 2130379007 10.0.0.36 59530 typ host", video_candidates[0]);
+ ASSERT_EQ("0 2 UDP 2130379006 10.0.0.36 64378 typ host", video_candidates[1]);
+ ASSERT_EQ("2 2 UDP 1694236670 24.6.134.204 64378 typ srflx raddr 10.0.0.36 rport 64378", video_candidates[2]);
+ ASSERT_EQ("6 2 UDP 16515070 162.222.183.171 64941 typ relay raddr 162.222.183.171 rport 64941", video_candidates[3]);
+ ASSERT_EQ("6 1 UDP 16515071 162.222.183.171 64800 typ relay raddr 162.222.183.171 rport 64800", video_candidates[4]);
+ ASSERT_EQ("2 1 UDP 1694236671 24.6.134.204 59530 typ srflx raddr 10.0.0.36 rport 59530", video_candidates[5]);
+ ASSERT_EQ("3 1 UDP 100401151 162.222.183.171 62935 typ relay raddr 162.222.183.171 rport 62935", video_candidates[6]);
+ ASSERT_EQ("3 2 UDP 100401150 162.222.183.171 61026 typ relay raddr 162.222.183.171 rport 61026", video_candidates[7]);
+
+ ASSERT_FALSE(mSdp->GetMediaSection(2).GetAttributeList().HasAttribute(
+ SdpAttribute::kCandidateAttribute));
+}
+
+TEST_P(NewSdpTest, CheckMid) {
+ ParseSdp(kBasicAudioVideoOffer);
+ ASSERT_EQ("first", mSdp->GetMediaSection(0).GetAttributeList().GetMid());
+ ASSERT_EQ("second", mSdp->GetMediaSection(1).GetAttributeList().GetMid());
+ ASSERT_EQ("third", mSdp->GetMediaSection(2).GetAttributeList().GetMid());
+}
+
+TEST_P(NewSdpTest, CheckMsid) {
+ ParseSdp(kBasicAudioVideoOffer);
+ ASSERT_TRUE(mSdp->GetAttributeList().HasAttribute(
+ SdpAttribute::kMsidSemanticAttribute));
+ auto semantics = mSdp->GetAttributeList().GetMsidSemantic().mMsidSemantics;
+ ASSERT_EQ(2U, semantics.size());
+ ASSERT_EQ("WMS", semantics[0].semantic);
+ ASSERT_EQ(2U, semantics[0].msids.size());
+ ASSERT_EQ("stream", semantics[0].msids[0]);
+ ASSERT_EQ("streama", semantics[0].msids[1]);
+ ASSERT_EQ("foo", semantics[1].semantic);
+ ASSERT_EQ(1U, semantics[1].msids.size());
+ ASSERT_EQ("stream", semantics[1].msids[0]);
+
+
+ const SdpMsidAttributeList& msids1 =
+ mSdp->GetMediaSection(0).GetAttributeList().GetMsid();
+ ASSERT_EQ(1U, msids1.mMsids.size());
+ ASSERT_EQ("stream", msids1.mMsids[0].identifier);
+ ASSERT_EQ("track", msids1.mMsids[0].appdata);
+ const SdpMsidAttributeList& msids2 =
+ mSdp->GetMediaSection(1).GetAttributeList().GetMsid();
+ ASSERT_EQ(2U, msids2.mMsids.size());
+ ASSERT_EQ("streama", msids2.mMsids[0].identifier);
+ ASSERT_EQ("tracka", msids2.mMsids[0].appdata);
+ ASSERT_EQ("streamb", msids2.mMsids[1].identifier);
+ ASSERT_EQ("trackb", msids2.mMsids[1].appdata);
+ const SdpMsidAttributeList& msids3 =
+ mSdp->GetMediaSection(2).GetAttributeList().GetMsid();
+ ASSERT_EQ(1U, msids3.mMsids.size());
+ ASSERT_EQ("noappdata", msids3.mMsids[0].identifier);
+ ASSERT_EQ("", msids3.mMsids[0].appdata);
+}
+
+TEST_P(NewSdpTest, CheckRid)
+{
+ ParseSdp(kBasicAudioVideoOffer);
+ ASSERT_TRUE(!!mSdp);
+ ASSERT_EQ(3U, mSdp->GetMediaSectionCount()) << "Wrong number of media sections";
+
+ ASSERT_FALSE(mSdp->GetAttributeList().HasAttribute(
+ SdpAttribute::kRidAttribute));
+ ASSERT_FALSE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kRidAttribute));
+ ASSERT_TRUE(mSdp->GetMediaSection(1).GetAttributeList().HasAttribute(
+ SdpAttribute::kRidAttribute));
+ ASSERT_FALSE(mSdp->GetMediaSection(2).GetAttributeList().HasAttribute(
+ SdpAttribute::kRidAttribute));
+
+ const SdpRidAttributeList& rids =
+ mSdp->GetMediaSection(1).GetAttributeList().GetRid();
+
+ ASSERT_EQ(1U, rids.mRids.size());
+ ASSERT_EQ("bar", rids.mRids[0].id);
+ ASSERT_EQ(sdp::kRecv, rids.mRids[0].direction);
+ ASSERT_EQ(1U, rids.mRids[0].formats.size());
+ ASSERT_EQ(96U, rids.mRids[0].formats[0]);
+ ASSERT_EQ(800U, rids.mRids[0].constraints.maxWidth);
+ ASSERT_EQ(600U, rids.mRids[0].constraints.maxHeight);
+}
+
+TEST_P(NewSdpTest, CheckMediaLevelIceUfrag) {
+ ParseSdp(kBasicAudioVideoOffer);
+ ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+ ASSERT_EQ(3U, mSdp->GetMediaSectionCount()) << "Wrong number of media sections";
+
+ ASSERT_TRUE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kIceUfragAttribute, true));
+ ASSERT_EQ("00000000",
+ mSdp->GetMediaSection(0).GetAttributeList().GetIceUfrag());
+
+ ASSERT_TRUE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kIceUfragAttribute, false));
+
+ ASSERT_TRUE(mSdp->GetMediaSection(1).GetAttributeList().HasAttribute(
+ SdpAttribute::kIceUfragAttribute, true));
+ ASSERT_EQ("4a799b2e",
+ mSdp->GetMediaSection(1).GetAttributeList().GetIceUfrag());
+}
+
+TEST_P(NewSdpTest, CheckMediaLevelIcePwd) {
+ ParseSdp(kBasicAudioVideoOffer);
+ ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+ ASSERT_EQ(3U, mSdp->GetMediaSectionCount()) << "Wrong number of media sections";
+
+ ASSERT_TRUE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kIcePwdAttribute));
+ ASSERT_EQ("0000000000000000000000000000000",
+ mSdp->GetMediaSection(0).GetAttributeList().GetIcePwd());
+
+ ASSERT_TRUE(mSdp->GetMediaSection(1).GetAttributeList().HasAttribute(
+ SdpAttribute::kIcePwdAttribute));
+ ASSERT_EQ("e4cc12a910f106a0a744719425510e17",
+ mSdp->GetMediaSection(1).GetAttributeList().GetIcePwd());
+}
+
+TEST_P(NewSdpTest, CheckGroups) {
+ ParseSdp(kBasicAudioVideoOffer);
+ const SdpGroupAttributeList& group = mSdp->GetAttributeList().GetGroup();
+ const SdpGroupAttributeList::Group& group1 = group.mGroups[0];
+ ASSERT_EQ(SdpGroupAttributeList::kBundle, group1.semantics);
+ ASSERT_EQ(2U, group1.tags.size());
+ ASSERT_EQ("first", group1.tags[0]);
+ ASSERT_EQ("second", group1.tags[1]);
+
+ const SdpGroupAttributeList::Group& group2 = group.mGroups[1];
+ ASSERT_EQ(SdpGroupAttributeList::kBundle, group2.semantics);
+ ASSERT_EQ(1U, group2.tags.size());
+ ASSERT_EQ("third", group2.tags[0]);
+
+ const SdpGroupAttributeList::Group& group3 = group.mGroups[2];
+ ASSERT_EQ(SdpGroupAttributeList::kLs, group3.semantics);
+ ASSERT_EQ(2U, group3.tags.size());
+ ASSERT_EQ("first", group3.tags[0]);
+ ASSERT_EQ("third", group3.tags[1]);
+}
+
+// SDP from a basic A/V call with data channel FFX/FFX
+const std::string kBasicAudioVideoDataOffer =
+"v=0" CRLF
+"o=Mozilla-SIPUA-35.0a1 27987 0 IN IP4 0.0.0.0" CRLF
+"s=SIP Call" CRLF
+"t=0 0" CRLF
+"a=ice-ufrag:8a39d2ae" CRLF
+"a=ice-pwd:601d53aba51a318351b3ecf5ee00048f" CRLF
+"a=fingerprint:sha-256 30:FF:8E:2B:AC:9D:ED:70:18:10:67:C8:AE:9E:68:F3:86:53:51:B0:AC:31:B7:BE:6D:CF:A4:2E:D3:6E:B4:28" CRLF
+"m=audio 9 RTP/SAVPF 109 9 0 8 101" CRLF
+"c=IN IP4 0.0.0.0" CRLF
+"a=rtpmap:109 opus/48000/2" CRLF
+"a=ptime:20" CRLF
+"a=rtpmap:9 G722/8000" CRLF
+"a=rtpmap:0 PCMU/8000" CRLF
+"a=rtpmap:8 PCMA/8000" CRLF
+"a=rtpmap:101 telephone-event/8000" CRLF
+"a=fmtp:101 0-15" CRLF
+"a=sendrecv" CRLF
+"a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level" CRLF
+"a=extmap:2/sendonly some_extension" CRLF
+"a=extmap:3 some_other_extension some_params some more params" CRLF
+"a=setup:actpass" CRLF
+"a=rtcp-mux" CRLF
+"m=video 9 RTP/SAVPF 120 126 97" CRLF
+"c=IN IP4 0.0.0.0" CRLF
+"a=rtpmap:120 VP8/90000" CRLF
+"a=rtpmap:126 H264/90000" CRLF
+"a=fmtp:126 profile-level-id=42e01f;packetization-mode=1" CRLF
+"a=rtpmap:97 H264/90000" CRLF
+"a=fmtp:97 profile-level-id=42e01f" CRLF
+"a=sendrecv" CRLF
+// sipcc barfs on this, despite that it is valid syntax
+// Do we care about fixing?
+//"a=rtcp-fb:120 ack" CRLF // Should be ignored by sipcc
+"a=rtcp-fb:120 ack rpsi" CRLF
+"a=rtcp-fb:120 ack app foo" CRLF
+"a=rtcp-fb:120 ack foo" CRLF // Should be ignored
+"a=rtcp-fb:120 nack" CRLF
+"a=rtcp-fb:120 nack sli" CRLF
+"a=rtcp-fb:120 nack pli" CRLF
+"a=rtcp-fb:120 nack rpsi" CRLF
+"a=rtcp-fb:120 nack app foo" CRLF
+"a=rtcp-fb:120 nack foo" CRLF // Should be ignored
+"a=rtcp-fb:120 ccm fir" CRLF
+"a=rtcp-fb:120 ccm tmmbr" CRLF
+"a=rtcp-fb:120 ccm tstr" CRLF
+"a=rtcp-fb:120 ccm vbcm" CRLF
+"a=rtcp-fb:120 ccm foo" CRLF // Should be ignored
+"a=rtcp-fb:120 trr-int 10" CRLF
+"a=rtcp-fb:120 goog-remb" CRLF
+"a=rtcp-fb:120 foo" CRLF // Should be ignored
+"a=rtcp-fb:126 nack" CRLF
+"a=rtcp-fb:126 nack pli" CRLF
+"a=rtcp-fb:126 ccm fir" CRLF
+"a=rtcp-fb:97 nack" CRLF
+"a=rtcp-fb:97 nack pli" CRLF
+"a=rtcp-fb:97 ccm fir" CRLF
+"a=rtcp-fb:* ccm tmmbr" CRLF
+"a=setup:actpass" CRLF
+"a=rtcp-mux" CRLF
+"m=application 9 DTLS/SCTP 5000" CRLF
+"c=IN IP4 0.0.0.0" CRLF
+"a=sctpmap:5000 webrtc-datachannel 16" CRLF
+"a=setup:actpass" CRLF;
+
+TEST_P(NewSdpTest, BasicAudioVideoDataSdpParse) {
+ ParseSdp(kBasicAudioVideoDataOffer);
+ ASSERT_EQ(0U, mParser.GetParseErrors().size()) <<
+ "Got parse errors: " << GetParseErrors();
+}
+
+TEST_P(NewSdpTest, CheckApplicationParameters) {
+ ParseSdp(kBasicAudioVideoDataOffer);
+ ASSERT_TRUE(!!mSdp);
+ ASSERT_EQ(3U, mSdp->GetMediaSectionCount()) << "Wrong number of media sections";
+ ASSERT_EQ(SdpMediaSection::kAudio, mSdp->GetMediaSection(0).GetMediaType())
+ << "Wrong type for first media section";
+ ASSERT_EQ(SdpMediaSection::kVideo, mSdp->GetMediaSection(1).GetMediaType())
+ << "Wrong type for second media section";
+ ASSERT_EQ(SdpMediaSection::kApplication, mSdp->GetMediaSection(2).GetMediaType())
+ << "Wrong type for third media section";
+
+ ASSERT_EQ(SdpMediaSection::kDtlsSctp,
+ mSdp->GetMediaSection(2).GetProtocol())
+ << "Wrong protocol for application";
+ auto app_formats = mSdp->GetMediaSection(2).GetFormats();
+ ASSERT_EQ(1U, app_formats.size()) << "Wrong number of formats for audio";
+ ASSERT_EQ("5000", app_formats[0]);
+
+ const SdpConnection& conn3 = mSdp->GetMediaSection(2).GetConnection();
+ ASSERT_EQ(sdp::kIPv4, conn3.GetAddrType());
+ ASSERT_EQ("0.0.0.0", conn3.GetAddress());
+ ASSERT_EQ(0U, conn3.GetTtl());
+ ASSERT_EQ(0U, conn3.GetCount());
+
+ ASSERT_TRUE(mSdp->GetMediaSection(2).GetAttributeList().HasAttribute(
+ SdpAttribute::kSetupAttribute));
+ ASSERT_EQ(SdpSetupAttribute::kActpass,
+ mSdp->GetMediaSection(2).GetAttributeList().GetSetup().mRole);
+}
+
+TEST_P(NewSdpTest, CheckExtmap) {
+ ParseSdp(kBasicAudioVideoDataOffer);
+ ASSERT_TRUE(!!mSdp);
+ ASSERT_EQ(3U, mSdp->GetMediaSectionCount()) << "Wrong number of media sections";
+
+ ASSERT_TRUE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kExtmapAttribute));
+
+ auto extmaps =
+ mSdp->GetMediaSection(0).GetAttributeList().GetExtmap().mExtmaps;
+ ASSERT_EQ(3U, extmaps.size());
+
+ ASSERT_EQ(1U, extmaps[0].entry);
+ ASSERT_FALSE(extmaps[0].direction_specified);
+ ASSERT_EQ("urn:ietf:params:rtp-hdrext:ssrc-audio-level",
+ extmaps[0].extensionname);
+ ASSERT_EQ("",
+ extmaps[0].extensionattributes);
+
+ ASSERT_EQ(2U, extmaps[1].entry);
+ ASSERT_TRUE(extmaps[1].direction_specified);
+ ASSERT_EQ(SdpDirectionAttribute::kSendonly, extmaps[1].direction);
+ ASSERT_EQ("some_extension",
+ extmaps[1].extensionname);
+ ASSERT_EQ("",
+ extmaps[1].extensionattributes);
+
+ ASSERT_EQ(3U, extmaps[2].entry);
+ ASSERT_FALSE(extmaps[2].direction_specified);
+ ASSERT_EQ("some_other_extension",
+ extmaps[2].extensionname);
+ ASSERT_EQ("some_params some more params",
+ extmaps[2].extensionattributes);
+}
+
+TEST_P(NewSdpTest, CheckRtcpFb) {
+ ParseSdp(kBasicAudioVideoDataOffer);
+ ASSERT_TRUE(!!mSdp);
+ ASSERT_EQ(3U, mSdp->GetMediaSectionCount()) << "Wrong number of media sections";
+
+ auto& video_attrs = mSdp->GetMediaSection(1).GetAttributeList();
+ ASSERT_TRUE(video_attrs.HasAttribute(SdpAttribute::kRtcpFbAttribute));
+ auto& rtcpfbs = video_attrs.GetRtcpFb().mFeedbacks;
+ ASSERT_EQ(20U, rtcpfbs.size());
+ CheckRtcpFb(rtcpfbs[0], "120", SdpRtcpFbAttributeList::kAck, "rpsi");
+ CheckRtcpFb(rtcpfbs[1], "120", SdpRtcpFbAttributeList::kAck, "app", "foo");
+ CheckRtcpFb(rtcpfbs[2], "120", SdpRtcpFbAttributeList::kNack, "");
+ CheckRtcpFb(rtcpfbs[3], "120", SdpRtcpFbAttributeList::kNack, "sli");
+ CheckRtcpFb(rtcpfbs[4], "120", SdpRtcpFbAttributeList::kNack, "pli");
+ CheckRtcpFb(rtcpfbs[5], "120", SdpRtcpFbAttributeList::kNack, "rpsi");
+ CheckRtcpFb(rtcpfbs[6], "120", SdpRtcpFbAttributeList::kNack, "app", "foo");
+ CheckRtcpFb(rtcpfbs[7], "120", SdpRtcpFbAttributeList::kCcm, "fir");
+ CheckRtcpFb(rtcpfbs[8], "120", SdpRtcpFbAttributeList::kCcm, "tmmbr");
+ CheckRtcpFb(rtcpfbs[9], "120", SdpRtcpFbAttributeList::kCcm, "tstr");
+ CheckRtcpFb(rtcpfbs[10], "120", SdpRtcpFbAttributeList::kCcm, "vbcm");
+ CheckRtcpFb(rtcpfbs[11], "120", SdpRtcpFbAttributeList::kTrrInt, "10");
+ CheckRtcpFb(rtcpfbs[12], "120", SdpRtcpFbAttributeList::kRemb, "");
+ CheckRtcpFb(rtcpfbs[13], "126", SdpRtcpFbAttributeList::kNack, "");
+ CheckRtcpFb(rtcpfbs[14], "126", SdpRtcpFbAttributeList::kNack, "pli");
+ CheckRtcpFb(rtcpfbs[15], "126", SdpRtcpFbAttributeList::kCcm, "fir");
+ CheckRtcpFb(rtcpfbs[16], "97", SdpRtcpFbAttributeList::kNack, "");
+ CheckRtcpFb(rtcpfbs[17], "97", SdpRtcpFbAttributeList::kNack, "pli");
+ CheckRtcpFb(rtcpfbs[18], "97", SdpRtcpFbAttributeList::kCcm, "fir");
+ CheckRtcpFb(rtcpfbs[19], "*", SdpRtcpFbAttributeList::kCcm, "tmmbr");
+}
+
+TEST_P(NewSdpTest, CheckRtcp) {
+ ParseSdp(kBasicAudioVideoOffer);
+ ASSERT_TRUE(!!mSdp);
+ ASSERT_EQ(3U, mSdp->GetMediaSectionCount()) << "Wrong number of media sections";
+
+ ASSERT_FALSE(mSdp->GetAttributeList().HasAttribute(
+ SdpAttribute::kRtcpAttribute));
+ ASSERT_TRUE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kRtcpAttribute));
+ ASSERT_TRUE(mSdp->GetMediaSection(1).GetAttributeList().HasAttribute(
+ SdpAttribute::kRtcpAttribute));
+ ASSERT_FALSE(mSdp->GetMediaSection(2).GetAttributeList().HasAttribute(
+ SdpAttribute::kRtcpAttribute));
+
+ auto& rtcpAttr_0 = mSdp->GetMediaSection(0).GetAttributeList().GetRtcp();
+ ASSERT_EQ(62454U, rtcpAttr_0.mPort);
+ ASSERT_EQ(sdp::kInternet, rtcpAttr_0.mNetType);
+ ASSERT_EQ(sdp::kIPv4, rtcpAttr_0.mAddrType);
+ ASSERT_EQ("162.222.183.171", rtcpAttr_0.mAddress);
+
+ auto& rtcpAttr_1 = mSdp->GetMediaSection(1).GetAttributeList().GetRtcp();
+ ASSERT_EQ(61026U, rtcpAttr_1.mPort);
+ ASSERT_EQ("", rtcpAttr_1.mAddress);
+}
+
+TEST_P(NewSdpTest, CheckImageattr)
+{
+ ParseSdp(kBasicAudioVideoOffer);
+ ASSERT_TRUE(!!mSdp);
+ ASSERT_EQ(3U, mSdp->GetMediaSectionCount()) << "Wrong number of media sections";
+
+ ASSERT_FALSE(mSdp->GetAttributeList().HasAttribute(
+ SdpAttribute::kImageattrAttribute));
+ ASSERT_FALSE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kImageattrAttribute));
+ ASSERT_TRUE(mSdp->GetMediaSection(1).GetAttributeList().HasAttribute(
+ SdpAttribute::kImageattrAttribute));
+ ASSERT_FALSE(mSdp->GetMediaSection(2).GetAttributeList().HasAttribute(
+ SdpAttribute::kImageattrAttribute));
+
+ const SdpImageattrAttributeList& imageattrs =
+ mSdp->GetMediaSection(1).GetAttributeList().GetImageattr();
+
+ ASSERT_EQ(2U, imageattrs.mImageattrs.size());
+ const SdpImageattrAttributeList::Imageattr& imageattr_0(
+ imageattrs.mImageattrs[0]);
+ ASSERT_TRUE(imageattr_0.pt.isSome());
+ ASSERT_EQ(120U, *imageattr_0.pt);
+ ASSERT_TRUE(imageattr_0.sendAll);
+ ASSERT_TRUE(imageattr_0.recvAll);
+
+ const SdpImageattrAttributeList::Imageattr& imageattr_1(
+ imageattrs.mImageattrs[1]);
+ ASSERT_TRUE(imageattr_1.pt.isSome());
+ ASSERT_EQ(121U, *imageattr_1.pt);
+ ASSERT_FALSE(imageattr_1.sendAll);
+ ASSERT_FALSE(imageattr_1.recvAll);
+ ASSERT_EQ(1U, imageattr_1.sendSets.size());
+ ASSERT_EQ(1U, imageattr_1.sendSets[0].xRange.discreteValues.size());
+ ASSERT_EQ(640U, imageattr_1.sendSets[0].xRange.discreteValues.front());
+ ASSERT_EQ(1U, imageattr_1.sendSets[0].yRange.discreteValues.size());
+ ASSERT_EQ(480U, imageattr_1.sendSets[0].yRange.discreteValues.front());
+ ASSERT_EQ(1U, imageattr_1.recvSets.size());
+ ASSERT_EQ(1U, imageattr_1.recvSets[0].xRange.discreteValues.size());
+ ASSERT_EQ(640U, imageattr_1.recvSets[0].xRange.discreteValues.front());
+ ASSERT_EQ(1U, imageattr_1.recvSets[0].yRange.discreteValues.size());
+ ASSERT_EQ(480U, imageattr_1.recvSets[0].yRange.discreteValues.front());
+}
+
+TEST_P(NewSdpTest, CheckSimulcast)
+{
+ ParseSdp(kBasicAudioVideoOffer);
+ ASSERT_TRUE(!!mSdp);
+ ASSERT_EQ(3U, mSdp->GetMediaSectionCount()) << "Wrong number of media sections";
+
+ ASSERT_FALSE(mSdp->GetAttributeList().HasAttribute(
+ SdpAttribute::kSimulcastAttribute));
+ ASSERT_FALSE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kSimulcastAttribute));
+ ASSERT_TRUE(mSdp->GetMediaSection(1).GetAttributeList().HasAttribute(
+ SdpAttribute::kSimulcastAttribute));
+ ASSERT_FALSE(mSdp->GetMediaSection(2).GetAttributeList().HasAttribute(
+ SdpAttribute::kSimulcastAttribute));
+
+ const SdpSimulcastAttribute& simulcast =
+ mSdp->GetMediaSection(1).GetAttributeList().GetSimulcast();
+
+ ASSERT_EQ(2U, simulcast.recvVersions.size());
+ ASSERT_EQ(0U, simulcast.sendVersions.size());
+ ASSERT_EQ(1U, simulcast.recvVersions[0].choices.size());
+ ASSERT_EQ("120", simulcast.recvVersions[0].choices[0]);
+ ASSERT_EQ(1U, simulcast.recvVersions[1].choices.size());
+ ASSERT_EQ("121", simulcast.recvVersions[1].choices[0]);
+ ASSERT_EQ(SdpSimulcastAttribute::Versions::kPt,
+ simulcast.recvVersions.type);
+}
+
+TEST_P(NewSdpTest, CheckSctpmap) {
+ ParseSdp(kBasicAudioVideoDataOffer);
+ ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+ ASSERT_EQ(3U, mSdp->GetMediaSectionCount())
+ << "Wrong number of media sections";
+
+ const SdpMediaSection& appsec = mSdp->GetMediaSection(2);
+ ASSERT_TRUE(
+ appsec.GetAttributeList().HasAttribute(SdpAttribute::kSctpmapAttribute));
+ const SdpSctpmapAttributeList& sctpmap =
+ appsec.GetAttributeList().GetSctpmap();
+
+ ASSERT_EQ(1U, sctpmap.mSctpmaps.size())
+ << "Wrong number of sctpmap attributes";
+ ASSERT_EQ(1U, appsec.GetFormats().size());
+
+ // Need to know name of type
+ CheckSctpmap("5000",
+ "webrtc-datachannel",
+ 16,
+ appsec.GetFormats()[0],
+ sctpmap);
+}
+
+const std::string kNewSctpmapOfferDraft07 =
+"v=0" CRLF
+"o=Mozilla-SIPUA-35.0a1 27987 0 IN IP4 0.0.0.0" CRLF
+"s=SIP Call" CRLF
+"t=0 0" CRLF
+"a=ice-ufrag:8a39d2ae" CRLF
+"a=ice-pwd:601d53aba51a318351b3ecf5ee00048f" CRLF
+"a=fingerprint:sha-256 30:FF:8E:2B:AC:9D:ED:70:18:10:67:C8:AE:9E:68:F3:86:53:51:B0:AC:31:B7:BE:6D:CF:A4:2E:D3:6E:B4:28" CRLF
+"m=application 9 DTLS/SCTP webrtc-datachannel" CRLF
+"c=IN IP4 0.0.0.0" CRLF
+"a=fmtp:webrtc-datachannel max-message-size=100000" CRLF
+"a=sctp-port 5000" CRLF
+"a=setup:actpass" CRLF;
+
+TEST_P(NewSdpTest, NewSctpmapSdpParse) {
+ ParseSdp(kNewSctpmapOfferDraft07, false);
+}
+
+INSTANTIATE_TEST_CASE_P(RoundTripSerialize,
+ NewSdpTest,
+ ::testing::Values(false, true));
+
+const std::string kCandidateInSessionSDP =
+"v=0" CRLF
+"o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
+"s=SIP Call" CRLF
+"c=IN IP4 224.0.0.1/100/12" CRLF
+"t=0 0" CRLF
+"a=candidate:0 1 UDP 2130379007 10.0.0.36 62453 typ host" CRLF
+"m=audio 9 RTP/SAVPF 109 9 0 8 101" CRLF
+"c=IN IP4 0.0.0.0" CRLF
+"a=rtpmap:109 opus/48000/2" CRLF;
+
+// This may or may not parse, but if it does, the errant candidate attribute
+// should be ignored.
+TEST_P(NewSdpTest, CheckCandidateInSessionLevel) {
+ ParseSdp(kCandidateInSessionSDP, false);
+ if (mSdp) {
+ ASSERT_FALSE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kCandidateAttribute));
+ ASSERT_FALSE(mSdp->GetAttributeList().HasAttribute(
+ SdpAttribute::kCandidateAttribute));
+ }
+}
+
+const std::string kBundleOnlyInSessionSDP =
+"v=0" CRLF
+"o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
+"s=SIP Call" CRLF
+"c=IN IP4 224.0.0.1/100/12" CRLF
+"t=0 0" CRLF
+"a=bundle-only" CRLF
+"m=audio 9 RTP/SAVPF 109 9 0 8 101" CRLF
+"c=IN IP4 0.0.0.0" CRLF
+"a=rtpmap:109 opus/48000/2" CRLF;
+
+// This may or may not parse, but if it does, the errant attribute
+// should be ignored.
+TEST_P(NewSdpTest, CheckBundleOnlyInSessionLevel) {
+ ParseSdp(kBundleOnlyInSessionSDP, false);
+ if (mSdp) {
+ ASSERT_FALSE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kBundleOnlyAttribute));
+ ASSERT_FALSE(mSdp->GetAttributeList().HasAttribute(
+ SdpAttribute::kBundleOnlyAttribute));
+ }
+}
+
+const std::string kFmtpInSessionSDP =
+"v=0" CRLF
+"o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
+"s=SIP Call" CRLF
+"c=IN IP4 224.0.0.1/100/12" CRLF
+"t=0 0" CRLF
+"a=fmtp:109 0-15" CRLF
+"m=audio 9 RTP/SAVPF 109 9 0 8 101" CRLF
+"c=IN IP4 0.0.0.0" CRLF
+"a=rtpmap:109 opus/48000/2" CRLF;
+
+// This may or may not parse, but if it does, the errant attribute
+// should be ignored.
+TEST_P(NewSdpTest, CheckFmtpInSessionLevel) {
+ ParseSdp(kFmtpInSessionSDP, false);
+ if (mSdp) {
+ ASSERT_FALSE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kFmtpAttribute));
+ ASSERT_FALSE(mSdp->GetAttributeList().HasAttribute(
+ SdpAttribute::kFmtpAttribute));
+ }
+}
+
+const std::string kIceMismatchInSessionSDP =
+"v=0" CRLF
+"o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
+"s=SIP Call" CRLF
+"c=IN IP4 224.0.0.1/100/12" CRLF
+"t=0 0" CRLF
+"a=ice-mismatch" CRLF
+"m=audio 9 RTP/SAVPF 109 9 0 8 101" CRLF
+"c=IN IP4 0.0.0.0" CRLF
+"a=rtpmap:109 opus/48000/2" CRLF;
+
+// This may or may not parse, but if it does, the errant attribute
+// should be ignored.
+TEST_P(NewSdpTest, CheckIceMismatchInSessionLevel) {
+ ParseSdp(kIceMismatchInSessionSDP, false);
+ if (mSdp) {
+ ASSERT_FALSE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kIceMismatchAttribute));
+ ASSERT_FALSE(mSdp->GetAttributeList().HasAttribute(
+ SdpAttribute::kIceMismatchAttribute));
+ }
+}
+
+const std::string kImageattrInSessionSDP =
+"v=0" CRLF
+"o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
+"s=SIP Call" CRLF
+"c=IN IP4 224.0.0.1/100/12" CRLF
+"t=0 0" CRLF
+"a=imageattr:120 send * recv *" CRLF
+"m=video 9 RTP/SAVPF 120" CRLF
+"c=IN IP4 0.0.0.0" CRLF
+"a=rtpmap:120 VP8/90000" CRLF;
+
+// This may or may not parse, but if it does, the errant attribute
+// should be ignored.
+TEST_P(NewSdpTest, CheckImageattrInSessionLevel) {
+ ParseSdp(kImageattrInSessionSDP, false);
+ if (mSdp) {
+ ASSERT_FALSE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kImageattrAttribute));
+ ASSERT_FALSE(mSdp->GetAttributeList().HasAttribute(
+ SdpAttribute::kImageattrAttribute));
+ }
+}
+
+const std::string kLabelInSessionSDP =
+"v=0" CRLF
+"o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
+"s=SIP Call" CRLF
+"c=IN IP4 224.0.0.1/100/12" CRLF
+"t=0 0" CRLF
+"a=label:foobar" CRLF
+"m=video 9 RTP/SAVPF 120" CRLF
+"c=IN IP4 0.0.0.0" CRLF
+"a=rtpmap:120 VP8/90000" CRLF;
+
+// This may or may not parse, but if it does, the errant attribute
+// should be ignored.
+TEST_P(NewSdpTest, CheckLabelInSessionLevel) {
+ ParseSdp(kLabelInSessionSDP, false);
+ if (mSdp) {
+ ASSERT_FALSE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kLabelAttribute));
+ ASSERT_FALSE(mSdp->GetAttributeList().HasAttribute(
+ SdpAttribute::kLabelAttribute));
+ }
+}
+
+const std::string kMaxptimeInSessionSDP =
+"v=0" CRLF
+"o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
+"s=SIP Call" CRLF
+"c=IN IP4 224.0.0.1/100/12" CRLF
+"t=0 0" CRLF
+"a=maxptime:100" CRLF
+"m=video 9 RTP/SAVPF 120" CRLF
+"c=IN IP4 0.0.0.0" CRLF
+"a=rtpmap:120 VP8/90000" CRLF;
+
+// This may or may not parse, but if it does, the errant attribute
+// should be ignored.
+TEST_P(NewSdpTest, CheckMaxptimeInSessionLevel) {
+ ParseSdp(kMaxptimeInSessionSDP, false);
+ if (mSdp) {
+ ASSERT_FALSE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kMaxptimeAttribute));
+ ASSERT_FALSE(mSdp->GetAttributeList().HasAttribute(
+ SdpAttribute::kMaxptimeAttribute));
+ }
+}
+
+const std::string kMidInSessionSDP =
+"v=0" CRLF
+"o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
+"s=SIP Call" CRLF
+"c=IN IP4 224.0.0.1/100/12" CRLF
+"t=0 0" CRLF
+"a=mid:foobar" CRLF
+"m=video 9 RTP/SAVPF 120" CRLF
+"c=IN IP4 0.0.0.0" CRLF
+"a=rtpmap:120 VP8/90000" CRLF;
+
+// This may or may not parse, but if it does, the errant attribute
+// should be ignored.
+TEST_P(NewSdpTest, CheckMidInSessionLevel) {
+ ParseSdp(kMidInSessionSDP, false);
+ if (mSdp) {
+ ASSERT_FALSE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kMidAttribute));
+ ASSERT_FALSE(mSdp->GetAttributeList().HasAttribute(
+ SdpAttribute::kMidAttribute));
+ }
+}
+
+const std::string kMsidInSessionSDP =
+"v=0" CRLF
+"o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
+"s=SIP Call" CRLF
+"c=IN IP4 224.0.0.1/100/12" CRLF
+"t=0 0" CRLF
+"a=msid:foobar" CRLF
+"m=video 9 RTP/SAVPF 120" CRLF
+"c=IN IP4 0.0.0.0" CRLF
+"a=rtpmap:120 VP8/90000" CRLF;
+
+// This may or may not parse, but if it does, the errant attribute
+// should be ignored.
+TEST_P(NewSdpTest, CheckMsidInSessionLevel) {
+ ParseSdp(kMsidInSessionSDP, false);
+ if (mSdp) {
+ ASSERT_FALSE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kMsidAttribute));
+ ASSERT_FALSE(mSdp->GetAttributeList().HasAttribute(
+ SdpAttribute::kMsidAttribute));
+ }
+}
+
+const std::string kPtimeInSessionSDP =
+"v=0" CRLF
+"o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
+"s=SIP Call" CRLF
+"c=IN IP4 224.0.0.1/100/12" CRLF
+"t=0 0" CRLF
+"a=ptime:50" CRLF
+"m=video 9 RTP/SAVPF 120" CRLF
+"c=IN IP4 0.0.0.0" CRLF
+"a=rtpmap:120 VP8/90000" CRLF;
+
+// This may or may not parse, but if it does, the errant attribute
+// should be ignored.
+TEST_P(NewSdpTest, CheckPtimeInSessionLevel) {
+ ParseSdp(kPtimeInSessionSDP, false);
+ if (mSdp) {
+ ASSERT_FALSE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kPtimeAttribute));
+ ASSERT_FALSE(mSdp->GetAttributeList().HasAttribute(
+ SdpAttribute::kPtimeAttribute));
+ }
+}
+
+const std::string kRemoteCandidatesInSessionSDP =
+"v=0" CRLF
+"o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
+"s=SIP Call" CRLF
+"c=IN IP4 224.0.0.1/100/12" CRLF
+"t=0 0" CRLF
+"a=remote-candidates:0 10.0.0.1 5555" CRLF
+"m=video 9 RTP/SAVPF 120" CRLF
+"c=IN IP4 0.0.0.0" CRLF
+"a=rtpmap:120 VP8/90000" CRLF;
+
+// This may or may not parse, but if it does, the errant attribute
+// should be ignored.
+TEST_P(NewSdpTest, CheckRemoteCandidatesInSessionLevel) {
+ ParseSdp(kRemoteCandidatesInSessionSDP, false);
+ if (mSdp) {
+ ASSERT_FALSE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kRemoteCandidatesAttribute));
+ ASSERT_FALSE(mSdp->GetAttributeList().HasAttribute(
+ SdpAttribute::kRemoteCandidatesAttribute));
+ }
+}
+
+const std::string kRtcpInSessionSDP =
+"v=0" CRLF
+"o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
+"s=SIP Call" CRLF
+"c=IN IP4 224.0.0.1/100/12" CRLF
+"t=0 0" CRLF
+"a=rtcp:5555" CRLF
+"m=video 9 RTP/SAVPF 120" CRLF
+"c=IN IP4 0.0.0.0" CRLF
+"a=rtpmap:120 VP8/90000" CRLF;
+
+// This may or may not parse, but if it does, the errant attribute
+// should be ignored.
+TEST_P(NewSdpTest, CheckRtcpInSessionLevel) {
+ ParseSdp(kRtcpInSessionSDP, false);
+ if (mSdp) {
+ ASSERT_FALSE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kRtcpAttribute));
+ ASSERT_FALSE(mSdp->GetAttributeList().HasAttribute(
+ SdpAttribute::kRtcpAttribute));
+ }
+}
+
+const std::string kRtcpFbInSessionSDP =
+"v=0" CRLF
+"o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
+"s=SIP Call" CRLF
+"c=IN IP4 224.0.0.1/100/12" CRLF
+"t=0 0" CRLF
+"a=rtcp-fb:120 nack" CRLF
+"m=video 9 RTP/SAVPF 120" CRLF
+"c=IN IP4 0.0.0.0" CRLF
+"a=rtpmap:120 VP8/90000" CRLF;
+
+// This may or may not parse, but if it does, the errant attribute
+// should be ignored.
+TEST_P(NewSdpTest, CheckRtcpFbInSessionLevel) {
+ ParseSdp(kRtcpFbInSessionSDP, false);
+ if (mSdp) {
+ ASSERT_FALSE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kRtcpFbAttribute));
+ ASSERT_FALSE(mSdp->GetAttributeList().HasAttribute(
+ SdpAttribute::kRtcpFbAttribute));
+ }
+}
+
+const std::string kRtcpMuxInSessionSDP =
+"v=0" CRLF
+"o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
+"s=SIP Call" CRLF
+"c=IN IP4 224.0.0.1/100/12" CRLF
+"t=0 0" CRLF
+"a=rtcp-mux" CRLF
+"m=video 9 RTP/SAVPF 120" CRLF
+"c=IN IP4 0.0.0.0" CRLF
+"a=rtpmap:120 VP8/90000" CRLF;
+
+// This may or may not parse, but if it does, the errant attribute
+// should be ignored.
+TEST_P(NewSdpTest, CheckRtcpMuxInSessionLevel) {
+ ParseSdp(kRtcpMuxInSessionSDP, false);
+ if (mSdp) {
+ ASSERT_FALSE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kRtcpMuxAttribute));
+ ASSERT_FALSE(mSdp->GetAttributeList().HasAttribute(
+ SdpAttribute::kRtcpMuxAttribute));
+ }
+}
+
+const std::string kRtcpRsizeInSessionSDP =
+"v=0" CRLF
+"o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
+"s=SIP Call" CRLF
+"c=IN IP4 224.0.0.1/100/12" CRLF
+"t=0 0" CRLF
+"a=rtcp-rsize" CRLF
+"m=video 9 RTP/SAVPF 120" CRLF
+"c=IN IP4 0.0.0.0" CRLF
+"a=rtpmap:120 VP8/90000" CRLF;
+
+// This may or may not parse, but if it does, the errant attribute
+// should be ignored.
+TEST_P(NewSdpTest, CheckRtcpRsizeInSessionLevel) {
+ ParseSdp(kRtcpRsizeInSessionSDP, false);
+ if (mSdp) {
+ ASSERT_FALSE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kRtcpRsizeAttribute));
+ ASSERT_FALSE(mSdp->GetAttributeList().HasAttribute(
+ SdpAttribute::kRtcpRsizeAttribute));
+ }
+}
+
+const std::string kRtpmapInSessionSDP =
+"v=0" CRLF
+"o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
+"s=SIP Call" CRLF
+"c=IN IP4 224.0.0.1/100/12" CRLF
+"t=0 0" CRLF
+"a=rtpmap:120 VP8/90000" CRLF
+"m=video 9 RTP/SAVPF 120" CRLF
+"c=IN IP4 0.0.0.0" CRLF;
+
+// This may or may not parse, but if it does, the errant attribute
+// should be ignored.
+TEST_P(NewSdpTest, CheckRtpmapInSessionLevel) {
+ ParseSdp(kRtpmapInSessionSDP, false);
+ if (mSdp) {
+ ASSERT_FALSE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kRtpmapAttribute));
+ ASSERT_FALSE(mSdp->GetAttributeList().HasAttribute(
+ SdpAttribute::kRtpmapAttribute));
+ }
+}
+
+const std::string kSctpmapInSessionSDP =
+"v=0" CRLF
+"o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
+"s=SIP Call" CRLF
+"c=IN IP4 224.0.0.1/100/12" CRLF
+"t=0 0" CRLF
+"a=sctpmap:5000" CRLF
+"m=video 9 RTP/SAVPF 120" CRLF
+"c=IN IP4 0.0.0.0" CRLF
+"a=rtpmap:120 VP8/90000" CRLF;
+
+// This may or may not parse, but if it does, the errant attribute
+// should be ignored.
+TEST_P(NewSdpTest, CheckSctpmapInSessionLevel) {
+ ParseSdp(kSctpmapInSessionSDP, false);
+ if (mSdp) {
+ ASSERT_FALSE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kSctpmapAttribute));
+ ASSERT_FALSE(mSdp->GetAttributeList().HasAttribute(
+ SdpAttribute::kSctpmapAttribute));
+ }
+}
+
+const std::string kSsrcInSessionSDP =
+"v=0" CRLF
+"o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
+"s=SIP Call" CRLF
+"c=IN IP4 224.0.0.1/100/12" CRLF
+"t=0 0" CRLF
+"a=ssrc:5000" CRLF
+"m=video 9 RTP/SAVPF 120" CRLF
+"c=IN IP4 0.0.0.0" CRLF
+"a=rtpmap:120 VP8/90000" CRLF;
+
+// This may or may not parse, but if it does, the errant attribute
+// should be ignored.
+TEST_P(NewSdpTest, CheckSsrcInSessionLevel) {
+ ParseSdp(kSsrcInSessionSDP, false);
+ if (mSdp) {
+ ASSERT_FALSE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kSsrcAttribute));
+ ASSERT_FALSE(mSdp->GetAttributeList().HasAttribute(
+ SdpAttribute::kSsrcAttribute));
+ }
+}
+
+const std::string kSsrcGroupInSessionSDP =
+"v=0" CRLF
+"o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
+"s=SIP Call" CRLF
+"c=IN IP4 224.0.0.1/100/12" CRLF
+"t=0 0" CRLF
+"a=ssrc-group:FID 5000" CRLF
+"m=video 9 RTP/SAVPF 120" CRLF
+"c=IN IP4 0.0.0.0" CRLF
+"a=rtpmap:120 VP8/90000" CRLF;
+
+// This may or may not parse, but if it does, the errant attribute
+// should be ignored.
+TEST_P(NewSdpTest, CheckSsrcGroupInSessionLevel) {
+ ParseSdp(kSsrcGroupInSessionSDP, false);
+ if (mSdp) {
+ ASSERT_FALSE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kSsrcGroupAttribute));
+ ASSERT_FALSE(mSdp->GetAttributeList().HasAttribute(
+ SdpAttribute::kSsrcGroupAttribute));
+ }
+}
+
+const std::string kMalformedImageattr =
+"v=0" CRLF
+"o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
+"s=SIP Call" CRLF
+"c=IN IP4 224.0.0.1/100/12" CRLF
+"t=0 0" CRLF
+"m=video 9 RTP/SAVPF 120" CRLF
+"c=IN IP4 0.0.0.0" CRLF
+"a=rtpmap:120 VP8/90000" CRLF
+"a=imageattr:flob" CRLF;
+
+TEST_P(NewSdpTest, CheckMalformedImageattr)
+{
+ if (GetParam()) {
+ // Don't do a parse/serialize before running this test
+ return;
+ }
+
+ ParseSdp(kMalformedImageattr, false);
+ ASSERT_NE("", GetParseErrors());
+}
+
+TEST_P(NewSdpTest, ParseInvalidSimulcastNoSuchSendRid) {
+ ParseSdp("v=0" CRLF
+ "o=- 4294967296 2 IN IP4 127.0.0.1" CRLF
+ "s=SIP Call" CRLF
+ "c=IN IP4 198.51.100.7" CRLF
+ "b=CT:5000" CRLF
+ "t=0 0" CRLF
+ "m=video 56436 RTP/SAVPF 120" CRLF
+ "a=rtpmap:120 VP8/90000" CRLF
+ "a=sendrecv" CRLF
+ "a=simulcast: send rid=9" CRLF,
+ false);
+ ASSERT_NE("", GetParseErrors());
+}
+
+TEST_P(NewSdpTest, ParseInvalidSimulcastNoSuchRecvRid) {
+ ParseSdp("v=0" CRLF
+ "o=- 4294967296 2 IN IP4 127.0.0.1" CRLF
+ "s=SIP Call" CRLF
+ "c=IN IP4 198.51.100.7" CRLF
+ "b=CT:5000" CRLF
+ "t=0 0" CRLF
+ "m=video 56436 RTP/SAVPF 120" CRLF
+ "a=rtpmap:120 VP8/90000" CRLF
+ "a=sendrecv" CRLF
+ "a=simulcast: recv rid=9" CRLF,
+ false);
+ ASSERT_NE("", GetParseErrors());
+}
+
+TEST_P(NewSdpTest, ParseInvalidSimulcastNoSuchPt) {
+ ParseSdp("v=0" CRLF
+ "o=- 4294967296 2 IN IP4 127.0.0.1" CRLF
+ "s=SIP Call" CRLF
+ "c=IN IP4 198.51.100.7" CRLF
+ "b=CT:5000" CRLF
+ "t=0 0" CRLF
+ "m=video 56436 RTP/SAVPF 120" CRLF
+ "a=rtpmap:120 VP8/90000" CRLF
+ "a=sendrecv" CRLF
+ "a=simulcast: send pt=9" CRLF,
+ false);
+ ASSERT_NE("", GetParseErrors());
+}
+
+TEST_P(NewSdpTest, ParseInvalidSimulcastNotSending) {
+ ParseSdp("v=0" CRLF
+ "o=- 4294967296 2 IN IP4 127.0.0.1" CRLF
+ "s=SIP Call" CRLF
+ "c=IN IP4 198.51.100.7" CRLF
+ "b=CT:5000" CRLF
+ "t=0 0" CRLF
+ "m=video 56436 RTP/SAVPF 120" CRLF
+ "a=rtpmap:120 VP8/90000" CRLF
+ "a=recvonly" CRLF
+ "a=simulcast: send pt=120" CRLF,
+ false);
+ ASSERT_NE("", GetParseErrors());
+}
+
+TEST_P(NewSdpTest, ParseInvalidSimulcastNotReceiving) {
+ ParseSdp("v=0" CRLF
+ "o=- 4294967296 2 IN IP4 127.0.0.1" CRLF
+ "s=SIP Call" CRLF
+ "c=IN IP4 198.51.100.7" CRLF
+ "b=CT:5000" CRLF
+ "t=0 0" CRLF
+ "m=video 56436 RTP/SAVPF 120" CRLF
+ "a=rtpmap:120 VP8/90000" CRLF
+ "a=sendonly" CRLF
+ "a=simulcast: recv pt=120" CRLF,
+ false);
+ ASSERT_NE("", GetParseErrors());
+}
+
+const std::string kNoAttributes =
+"v=0" CRLF
+"o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
+"s=SIP Call" CRLF
+"c=IN IP4 224.0.0.1/100/12" CRLF
+"t=0 0" CRLF
+"m=video 9 RTP/SAVPF 120" CRLF
+"c=IN IP4 0.0.0.0" CRLF
+"a=rtpmap:120 VP8/90000" CRLF;
+
+TEST_P(NewSdpTest, CheckNoAttributes) {
+ ParseSdp(kNoAttributes);
+
+ for (auto a = static_cast<size_t>(SdpAttribute::kFirstAttribute);
+ a <= static_cast<size_t>(SdpAttribute::kLastAttribute);
+ ++a) {
+
+ SdpAttribute::AttributeType type =
+ static_cast<SdpAttribute::AttributeType>(a);
+
+ // rtpmap is a special case right now, we throw parse errors if it is
+ // missing, and then insert one.
+ // direction is another special case that gets a default if not present
+ if (type != SdpAttribute::kRtpmapAttribute &&
+ type != SdpAttribute::kDirectionAttribute) {
+ ASSERT_FALSE(
+ mSdp->GetMediaSection(0).GetAttributeList().HasAttribute(type))
+ << "Attribute " << a << " should not have been present at media level";
+ ASSERT_FALSE(mSdp->GetAttributeList().HasAttribute(type))
+ << "Attribute " << a << " should not have been present at session level";
+ }
+ }
+
+ ASSERT_FALSE(mSdp->GetAttributeList().HasAttribute(
+ SdpAttribute::kRtpmapAttribute));
+
+ ASSERT_TRUE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kDirectionAttribute));
+ ASSERT_EQ(SdpDirectionAttribute::kSendrecv,
+ mSdp->GetMediaSection(0).GetAttributeList().GetDirection());
+ ASSERT_TRUE(mSdp->GetAttributeList().HasAttribute(
+ SdpAttribute::kDirectionAttribute));
+ ASSERT_EQ(SdpDirectionAttribute::kSendrecv,
+ mSdp->GetAttributeList().GetDirection());
+}
+
+
+const std::string kMediaLevelDtlsMessage =
+"v=0" CRLF
+"o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF
+"s=SIP Call" CRLF
+"c=IN IP4 224.0.0.1/100/12" CRLF
+"t=0 0" CRLF
+"m=video 9 RTP/SAVPF 120" CRLF
+"c=IN IP4 0.0.0.0" CRLF
+"a=dtls-message:client " BASE64_DTLS_HELLO CRLF
+"a=rtpmap:120 VP8/90000" CRLF;
+
+TEST_P(NewSdpTest, CheckMediaLevelDtlsMessage) {
+ ParseSdp(kMediaLevelDtlsMessage);
+ ASSERT_TRUE(!!mSdp) << "Parse failed: " << GetParseErrors();
+
+ // dtls-message is not defined for use at the media level; we don't
+ // parse it
+ ASSERT_FALSE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute(
+ SdpAttribute::kDtlsMessageAttribute));
+}
+
+
+TEST(NewSdpTestNoFixture, CheckAttributeTypeSerialize) {
+ for (auto a = static_cast<size_t>(SdpAttribute::kFirstAttribute);
+ a <= static_cast<size_t>(SdpAttribute::kLastAttribute);
+ ++a) {
+
+ SdpAttribute::AttributeType type =
+ static_cast<SdpAttribute::AttributeType>(a);
+
+ // Direction attributes are handled a little differently
+ if (type != SdpAttribute::kDirectionAttribute) {
+ std::ostringstream os;
+ os << type;
+ ASSERT_NE("", os.str());
+ }
+ }
+}
+
+static SdpImageattrAttributeList::XYRange
+ParseXYRange(const std::string& input)
+{
+ std::istringstream is(input + ",");
+ std::string error;
+ SdpImageattrAttributeList::XYRange range;
+ EXPECT_TRUE(range.Parse(is, &error)) << error;
+ EXPECT_EQ(',', is.get());
+ EXPECT_EQ(EOF, is.get());
+ return range;
+}
+
+TEST(NewSdpTestNoFixture, CheckImageattrXYRangeParseValid)
+{
+ {
+ SdpImageattrAttributeList::XYRange range(ParseXYRange("640"));
+ ASSERT_EQ(1U, range.discreteValues.size());
+ ASSERT_EQ(640U, range.discreteValues[0]);
+ }
+
+ {
+ SdpImageattrAttributeList::XYRange range(ParseXYRange("[320,640]"));
+ ASSERT_EQ(2U, range.discreteValues.size());
+ ASSERT_EQ(320U, range.discreteValues[0]);
+ ASSERT_EQ(640U, range.discreteValues[1]);
+ }
+
+ {
+ SdpImageattrAttributeList::XYRange range(ParseXYRange("[320,640,1024]"));
+ ASSERT_EQ(3U, range.discreteValues.size());
+ ASSERT_EQ(320U, range.discreteValues[0]);
+ ASSERT_EQ(640U, range.discreteValues[1]);
+ ASSERT_EQ(1024U, range.discreteValues[2]);
+ }
+
+ {
+ SdpImageattrAttributeList::XYRange range(ParseXYRange("[320:640]"));
+ ASSERT_EQ(0U, range.discreteValues.size());
+ ASSERT_EQ(320U, range.min);
+ ASSERT_EQ(1U, range.step);
+ ASSERT_EQ(640U, range.max);
+ }
+
+ {
+ SdpImageattrAttributeList::XYRange range(ParseXYRange("[320:16:640]"));
+ ASSERT_EQ(0U, range.discreteValues.size());
+ ASSERT_EQ(320U, range.min);
+ ASSERT_EQ(16U, range.step);
+ ASSERT_EQ(640U, range.max);
+ }
+}
+
+template<typename T>
+void
+ParseInvalid(const std::string& input, size_t last)
+{
+ std::istringstream is(input);
+ T parsed;
+ std::string error;
+ ASSERT_FALSE(parsed.Parse(is, &error))
+ << "\'" << input << "\' should not have parsed successfully";
+ is.clear();
+ ASSERT_EQ(last, static_cast<size_t>(is.tellg()))
+ << "Parse failed at unexpected location:" << std::endl
+ << input << std::endl
+ << std::string(is.tellg(), ' ') << "^" << std::endl;
+ // For a human to eyeball to make sure the error strings look sane
+ std::cout << "\"" << input << "\" - " << error << std::endl; \
+}
+
+TEST(NewSdpTestNoFixture, CheckImageattrXYRangeParseInvalid)
+{
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[-1", 1);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[-", 1);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[-v", 1);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[640:-1", 5);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[640:16:-1", 8);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[640,-1", 5);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[640,-]", 5);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("-v", 0);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("-1", 0);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("", 0);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[", 1);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[v", 1);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[", 1);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[ 640", 1);
+ // It looks like the overflow detection only happens once the whole number
+ // is scanned...
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[99999999999999999:", 18);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[640", 4);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[640:", 5);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[640:v", 5);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[640:16", 7);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[640:16:", 8);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[640:16:v", 8);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[640:16:320]", 11);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[640:16:320", 11);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[640:16:320v", 11);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[640:1024", 9);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[640:320]", 8);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[640:1024v", 9);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[640,", 5);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[640,v", 5);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[640]", 4);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[640x", 4);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("[640,]", 5);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>(" ", 0);
+ ParseInvalid<SdpImageattrAttributeList::XYRange>("v", 0);
+}
+
+static SdpImageattrAttributeList::SRange
+ParseSRange(const std::string& input)
+{
+ std::istringstream is(input + ",");
+ std::string error;
+ SdpImageattrAttributeList::SRange range;
+ EXPECT_TRUE(range.Parse(is, &error)) << error;
+ EXPECT_EQ(',', is.get());
+ EXPECT_EQ(EOF, is.get());
+ return range;
+}
+
+TEST(NewSdpTestNoFixture, CheckImageattrSRangeParseValid)
+{
+ {
+ SdpImageattrAttributeList::SRange range(ParseSRange("0.1"));
+ ASSERT_EQ(1U, range.discreteValues.size());
+ ASSERT_FLOAT_EQ(0.1f, range.discreteValues[0]);
+ }
+
+ {
+ SdpImageattrAttributeList::SRange range(ParseSRange("[0.1,0.2]"));
+ ASSERT_EQ(2U, range.discreteValues.size());
+ ASSERT_FLOAT_EQ(0.1f, range.discreteValues[0]);
+ ASSERT_FLOAT_EQ(0.2f, range.discreteValues[1]);
+ }
+
+ {
+ SdpImageattrAttributeList::SRange range(ParseSRange("[0.1,0.2,0.3]"));
+ ASSERT_EQ(3U, range.discreteValues.size());
+ ASSERT_FLOAT_EQ(0.1f, range.discreteValues[0]);
+ ASSERT_FLOAT_EQ(0.2f, range.discreteValues[1]);
+ ASSERT_FLOAT_EQ(0.3f, range.discreteValues[2]);
+ }
+
+ {
+ SdpImageattrAttributeList::SRange range(ParseSRange("[0.1-0.2]"));
+ ASSERT_EQ(0U, range.discreteValues.size());
+ ASSERT_FLOAT_EQ(0.1f, range.min);
+ ASSERT_FLOAT_EQ(0.2f, range.max);
+ }
+}
+
+TEST(NewSdpTestNoFixture, CheckImageattrSRangeParseInvalid)
+{
+ ParseInvalid<SdpImageattrAttributeList::SRange>("", 0);
+ ParseInvalid<SdpImageattrAttributeList::SRange>("[", 1);
+ ParseInvalid<SdpImageattrAttributeList::SRange>("[v", 1);
+ ParseInvalid<SdpImageattrAttributeList::SRange>("[-1", 1);
+ ParseInvalid<SdpImageattrAttributeList::SRange>("[", 1);
+ ParseInvalid<SdpImageattrAttributeList::SRange>("[-", 1);
+ ParseInvalid<SdpImageattrAttributeList::SRange>("[v", 1);
+ ParseInvalid<SdpImageattrAttributeList::SRange>("[ 0.2", 1);
+ ParseInvalid<SdpImageattrAttributeList::SRange>("[10.1-", 5);
+ ParseInvalid<SdpImageattrAttributeList::SRange>("[0.08-", 5);
+ ParseInvalid<SdpImageattrAttributeList::SRange>("[0.2", 4);
+ ParseInvalid<SdpImageattrAttributeList::SRange>("[0.2-", 5);
+ ParseInvalid<SdpImageattrAttributeList::SRange>("[0.2-v", 5);
+ ParseInvalid<SdpImageattrAttributeList::SRange>("[0.2--1", 5);
+ ParseInvalid<SdpImageattrAttributeList::SRange>("[0.2-0.3", 8);
+ ParseInvalid<SdpImageattrAttributeList::SRange>("[0.2-0.1]", 8);
+ ParseInvalid<SdpImageattrAttributeList::SRange>("[0.2-0.3v", 8);
+ ParseInvalid<SdpImageattrAttributeList::SRange>("[0.2,", 5);
+ ParseInvalid<SdpImageattrAttributeList::SRange>("[0.2,v", 5);
+ ParseInvalid<SdpImageattrAttributeList::SRange>("[0.2,-1", 5);
+ ParseInvalid<SdpImageattrAttributeList::SRange>("[0.2]", 4);
+ ParseInvalid<SdpImageattrAttributeList::SRange>("[0.2v", 4);
+ ParseInvalid<SdpImageattrAttributeList::SRange>("[0.2,]", 5);
+ ParseInvalid<SdpImageattrAttributeList::SRange>("[0.2,-]", 5);
+ ParseInvalid<SdpImageattrAttributeList::SRange>(" ", 0);
+ ParseInvalid<SdpImageattrAttributeList::SRange>("v", 0);
+ ParseInvalid<SdpImageattrAttributeList::SRange>("-v", 0);
+ ParseInvalid<SdpImageattrAttributeList::SRange>("-1", 0);
+}
+
+static SdpImageattrAttributeList::PRange
+ParsePRange(const std::string& input)
+{
+ std::istringstream is(input + ",");
+ std::string error;
+ SdpImageattrAttributeList::PRange range;
+ EXPECT_TRUE(range.Parse(is, &error)) << error;
+ EXPECT_EQ(',', is.get());
+ EXPECT_EQ(EOF, is.get());
+ return range;
+}
+
+TEST(NewSdpTestNoFixture, CheckImageattrPRangeParseValid)
+{
+ SdpImageattrAttributeList::PRange range(ParsePRange("[0.1000-9.9999]"));
+ ASSERT_FLOAT_EQ(0.1f, range.min);
+ ASSERT_FLOAT_EQ(9.9999f, range.max);
+}
+
+TEST(NewSdpTestNoFixture, CheckImageattrPRangeParseInvalid)
+{
+ ParseInvalid<SdpImageattrAttributeList::PRange>("", 0);
+ ParseInvalid<SdpImageattrAttributeList::PRange>("[", 1);
+ ParseInvalid<SdpImageattrAttributeList::PRange>("[v", 1);
+ ParseInvalid<SdpImageattrAttributeList::PRange>("[-1", 1);
+ ParseInvalid<SdpImageattrAttributeList::PRange>("[", 1);
+ ParseInvalid<SdpImageattrAttributeList::PRange>("[-", 1);
+ ParseInvalid<SdpImageattrAttributeList::PRange>("[v", 1);
+ ParseInvalid<SdpImageattrAttributeList::PRange>("[ 0.2", 1);
+ ParseInvalid<SdpImageattrAttributeList::PRange>("[10.1-", 5);
+ ParseInvalid<SdpImageattrAttributeList::PRange>("[0.08-", 5);
+ ParseInvalid<SdpImageattrAttributeList::PRange>("[0.2", 4);
+ ParseInvalid<SdpImageattrAttributeList::PRange>("[0.2-", 5);
+ ParseInvalid<SdpImageattrAttributeList::PRange>("[0.2-v", 5);
+ ParseInvalid<SdpImageattrAttributeList::PRange>("[0.2--1", 5);
+ ParseInvalid<SdpImageattrAttributeList::PRange>("[0.2-0.3", 8);
+ ParseInvalid<SdpImageattrAttributeList::PRange>("[0.2-0.1]", 8);
+ ParseInvalid<SdpImageattrAttributeList::PRange>("[0.2-0.3v", 8);
+ ParseInvalid<SdpImageattrAttributeList::PRange>("[0.2,", 4);
+ ParseInvalid<SdpImageattrAttributeList::PRange>("[0.2:", 4);
+ ParseInvalid<SdpImageattrAttributeList::PRange>("[0.2]", 4);
+ ParseInvalid<SdpImageattrAttributeList::PRange>("[0.2v", 4);
+ ParseInvalid<SdpImageattrAttributeList::PRange>(" ", 0);
+ ParseInvalid<SdpImageattrAttributeList::PRange>("v", 0);
+ ParseInvalid<SdpImageattrAttributeList::PRange>("-x", 0);
+ ParseInvalid<SdpImageattrAttributeList::PRange>("-1", 0);
+}
+
+static SdpImageattrAttributeList::Set
+ParseSet(const std::string& input)
+{
+ std::istringstream is(input + " ");
+ std::string error;
+ SdpImageattrAttributeList::Set set;
+ EXPECT_TRUE(set.Parse(is, &error)) << error;
+ EXPECT_EQ(' ', is.get());
+ EXPECT_EQ(EOF, is.get());
+ return set;
+}
+
+TEST(NewSdpTestNoFixture, CheckImageattrSetParseValid)
+{
+ {
+ SdpImageattrAttributeList::Set set(ParseSet("[x=320,y=240]"));
+ ASSERT_EQ(1U, set.xRange.discreteValues.size());
+ ASSERT_EQ(320U, set.xRange.discreteValues[0]);
+ ASSERT_EQ(1U, set.yRange.discreteValues.size());
+ ASSERT_EQ(240U, set.yRange.discreteValues[0]);
+ ASSERT_FALSE(set.sRange.IsSet());
+ ASSERT_FALSE(set.pRange.IsSet());
+ ASSERT_FLOAT_EQ(0.5f, set.qValue);
+ }
+
+ {
+ SdpImageattrAttributeList::Set set(ParseSet("[X=320,Y=240]"));
+ ASSERT_EQ(1U, set.xRange.discreteValues.size());
+ ASSERT_EQ(320U, set.xRange.discreteValues[0]);
+ ASSERT_EQ(1U, set.yRange.discreteValues.size());
+ ASSERT_EQ(240U, set.yRange.discreteValues[0]);
+ ASSERT_FALSE(set.sRange.IsSet());
+ ASSERT_FALSE(set.pRange.IsSet());
+ ASSERT_FLOAT_EQ(0.5f, set.qValue);
+ }
+
+ {
+ SdpImageattrAttributeList::Set set(ParseSet("[x=320,y=240,par=[0.1-0.2]]"));
+ ASSERT_EQ(1U, set.xRange.discreteValues.size());
+ ASSERT_EQ(320U, set.xRange.discreteValues[0]);
+ ASSERT_EQ(1U, set.yRange.discreteValues.size());
+ ASSERT_EQ(240U, set.yRange.discreteValues[0]);
+ ASSERT_FALSE(set.sRange.IsSet());
+ ASSERT_TRUE(set.pRange.IsSet());
+ ASSERT_FLOAT_EQ(0.1f, set.pRange.min);
+ ASSERT_FLOAT_EQ(0.2f, set.pRange.max);
+ ASSERT_FLOAT_EQ(0.5f, set.qValue);
+ }
+
+ {
+ SdpImageattrAttributeList::Set set(ParseSet("[x=320,y=240,sar=[0.1-0.2]]"));
+ ASSERT_EQ(1U, set.xRange.discreteValues.size());
+ ASSERT_EQ(320U, set.xRange.discreteValues[0]);
+ ASSERT_EQ(1U, set.yRange.discreteValues.size());
+ ASSERT_EQ(240U, set.yRange.discreteValues[0]);
+ ASSERT_TRUE(set.sRange.IsSet());
+ ASSERT_FLOAT_EQ(0.1f, set.sRange.min);
+ ASSERT_FLOAT_EQ(0.2f, set.sRange.max);
+ ASSERT_FALSE(set.pRange.IsSet());
+ ASSERT_FLOAT_EQ(0.5f, set.qValue);
+ }
+
+ {
+ SdpImageattrAttributeList::Set set(ParseSet("[x=320,y=240,q=0.1]"));
+ ASSERT_EQ(1U, set.xRange.discreteValues.size());
+ ASSERT_EQ(320U, set.xRange.discreteValues[0]);
+ ASSERT_EQ(1U, set.yRange.discreteValues.size());
+ ASSERT_EQ(240U, set.yRange.discreteValues[0]);
+ ASSERT_FALSE(set.sRange.IsSet());
+ ASSERT_FALSE(set.pRange.IsSet());
+ ASSERT_FLOAT_EQ(0.1f, set.qValue);
+ }
+
+ {
+ SdpImageattrAttributeList::Set set(
+ ParseSet("[x=320,y=240,par=[0.1-0.2],sar=[0.3-0.4],q=0.6]"));
+ ASSERT_EQ(1U, set.xRange.discreteValues.size());
+ ASSERT_EQ(320U, set.xRange.discreteValues[0]);
+ ASSERT_EQ(1U, set.yRange.discreteValues.size());
+ ASSERT_EQ(240U, set.yRange.discreteValues[0]);
+ ASSERT_TRUE(set.sRange.IsSet());
+ ASSERT_FLOAT_EQ(0.3f, set.sRange.min);
+ ASSERT_FLOAT_EQ(0.4f, set.sRange.max);
+ ASSERT_TRUE(set.pRange.IsSet());
+ ASSERT_FLOAT_EQ(0.1f, set.pRange.min);
+ ASSERT_FLOAT_EQ(0.2f, set.pRange.max);
+ ASSERT_FLOAT_EQ(0.6f, set.qValue);
+ }
+
+ {
+ SdpImageattrAttributeList::Set set(ParseSet("[x=320,y=240,foo=bar,q=0.1]"));
+ ASSERT_EQ(1U, set.xRange.discreteValues.size());
+ ASSERT_EQ(320U, set.xRange.discreteValues[0]);
+ ASSERT_EQ(1U, set.yRange.discreteValues.size());
+ ASSERT_EQ(240U, set.yRange.discreteValues[0]);
+ ASSERT_FALSE(set.sRange.IsSet());
+ ASSERT_FALSE(set.pRange.IsSet());
+ ASSERT_FLOAT_EQ(0.1f, set.qValue);
+ }
+
+ {
+ SdpImageattrAttributeList::Set set(
+ ParseSet("[x=320,y=240,foo=bar,q=0.1,bar=baz]"));
+ ASSERT_EQ(1U, set.xRange.discreteValues.size());
+ ASSERT_EQ(320U, set.xRange.discreteValues[0]);
+ ASSERT_EQ(1U, set.yRange.discreteValues.size());
+ ASSERT_EQ(240U, set.yRange.discreteValues[0]);
+ ASSERT_FALSE(set.sRange.IsSet());
+ ASSERT_FALSE(set.pRange.IsSet());
+ ASSERT_FLOAT_EQ(0.1f, set.qValue);
+ }
+
+ {
+ SdpImageattrAttributeList::Set set(
+ ParseSet("[x=320,y=240,foo=[bar],q=0.1,bar=[baz]]"));
+ ASSERT_EQ(1U, set.xRange.discreteValues.size());
+ ASSERT_EQ(320U, set.xRange.discreteValues[0]);
+ ASSERT_EQ(1U, set.yRange.discreteValues.size());
+ ASSERT_EQ(240U, set.yRange.discreteValues[0]);
+ ASSERT_FALSE(set.sRange.IsSet());
+ ASSERT_FALSE(set.pRange.IsSet());
+ ASSERT_FLOAT_EQ(0.1f, set.qValue);
+ }
+
+ {
+ SdpImageattrAttributeList::Set set(
+ ParseSet("[x=320,y=240,foo=[par=foo,sar=bar],q=0.1,bar=[baz]]"));
+ ASSERT_EQ(1U, set.xRange.discreteValues.size());
+ ASSERT_EQ(320U, set.xRange.discreteValues[0]);
+ ASSERT_EQ(1U, set.yRange.discreteValues.size());
+ ASSERT_EQ(240U, set.yRange.discreteValues[0]);
+ ASSERT_FALSE(set.sRange.IsSet());
+ ASSERT_FALSE(set.pRange.IsSet());
+ ASSERT_FLOAT_EQ(0.1f, set.qValue);
+ }
+}
+
+TEST(NewSdpTestNoFixture, CheckImageattrSetParseInvalid)
+{
+ ParseInvalid<SdpImageattrAttributeList::Set>("", 0);
+ ParseInvalid<SdpImageattrAttributeList::Set>("x", 0);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[", 1);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[=", 2);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[x", 2);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[y=", 3);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[x=[", 4);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[x=320", 6);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[x=320v", 6);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[x=320,", 7);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[x=320,=", 8);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[x=320,x", 8);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[x=320,x=", 9);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[x=320,y=[", 10);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[x=320,y=240", 12);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[x=320,y=240x", 12);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[x=320,y=240,", 13);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[x=320,y=240,q=", 15);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[x=320,y=240,q=v", 15);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[x=320,y=240,q=0.5", 18);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[x=320,y=240,q=0.5,", 19);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[x=320,y=240,q=0.5,]", 20);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[x=320,y=240,q=0.5,=]", 20);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[x=320,y=240,q=0.5,sar=v]", 23);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[x=320,y=240,q=0.5,q=0.4", 21);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[x=320,y=240,sar=", 17);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[x=320,y=240,sar=v", 17);
+ ParseInvalid<SdpImageattrAttributeList::Set>(
+ "[x=320,y=240,sar=[0.5-0.6],sar=[0.7-0.8]", 31);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[x=320,y=240,par=", 17);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[x=320,y=240,par=x", 17);
+ ParseInvalid<SdpImageattrAttributeList::Set>(
+ "[x=320,y=240,par=[0.5-0.6],par=[0.7-0.8]", 31);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[x=320,y=240,foo=", 17);
+ ParseInvalid<SdpImageattrAttributeList::Set>("[x=320,y=240,foo=x", 18);
+}
+
+static SdpImageattrAttributeList::Imageattr
+ParseImageattr(const std::string& input)
+{
+ std::istringstream is(input);
+ std::string error;
+ SdpImageattrAttributeList::Imageattr imageattr;
+ EXPECT_TRUE(imageattr.Parse(is, &error)) << error;
+ EXPECT_TRUE(is.eof());
+ return imageattr;
+}
+
+TEST(NewSdpTestNoFixture, CheckImageattrParseValid)
+{
+ {
+ SdpImageattrAttributeList::Imageattr imageattr(ParseImageattr("* send *"));
+ ASSERT_FALSE(imageattr.pt.isSome());
+ ASSERT_TRUE(imageattr.sendAll);
+ ASSERT_TRUE(imageattr.sendSets.empty());
+ ASSERT_FALSE(imageattr.recvAll);
+ ASSERT_TRUE(imageattr.recvSets.empty());
+ }
+
+ {
+ SdpImageattrAttributeList::Imageattr imageattr(ParseImageattr("* SEND *"));
+ ASSERT_FALSE(imageattr.pt.isSome());
+ ASSERT_TRUE(imageattr.sendAll);
+ ASSERT_TRUE(imageattr.sendSets.empty());
+ ASSERT_FALSE(imageattr.recvAll);
+ ASSERT_TRUE(imageattr.recvSets.empty());
+ }
+
+ {
+ SdpImageattrAttributeList::Imageattr imageattr(ParseImageattr("* recv *"));
+ ASSERT_FALSE(imageattr.pt.isSome());
+ ASSERT_FALSE(imageattr.sendAll);
+ ASSERT_TRUE(imageattr.sendSets.empty());
+ ASSERT_TRUE(imageattr.recvAll);
+ ASSERT_TRUE(imageattr.recvSets.empty());
+ }
+
+ {
+ SdpImageattrAttributeList::Imageattr imageattr(ParseImageattr("* RECV *"));
+ ASSERT_FALSE(imageattr.pt.isSome());
+ ASSERT_FALSE(imageattr.sendAll);
+ ASSERT_TRUE(imageattr.sendSets.empty());
+ ASSERT_TRUE(imageattr.recvAll);
+ ASSERT_TRUE(imageattr.recvSets.empty());
+ }
+
+ {
+ SdpImageattrAttributeList::Imageattr imageattr(
+ ParseImageattr("* recv * send *"));
+ ASSERT_FALSE(imageattr.pt.isSome());
+ ASSERT_TRUE(imageattr.sendAll);
+ ASSERT_TRUE(imageattr.sendSets.empty());
+ ASSERT_TRUE(imageattr.recvAll);
+ ASSERT_TRUE(imageattr.recvSets.empty());
+ }
+
+ {
+ SdpImageattrAttributeList::Imageattr imageattr(
+ ParseImageattr("* send * recv *"));
+ ASSERT_FALSE(imageattr.pt.isSome());
+ ASSERT_TRUE(imageattr.sendAll);
+ ASSERT_TRUE(imageattr.sendSets.empty());
+ ASSERT_TRUE(imageattr.recvAll);
+ ASSERT_TRUE(imageattr.recvSets.empty());
+ }
+
+ {
+ SdpImageattrAttributeList::Imageattr imageattr(
+ ParseImageattr("8 send * recv *"));
+ ASSERT_EQ(8U, *imageattr.pt);
+ ASSERT_TRUE(imageattr.sendAll);
+ ASSERT_TRUE(imageattr.sendSets.empty());
+ ASSERT_TRUE(imageattr.recvAll);
+ ASSERT_TRUE(imageattr.recvSets.empty());
+ }
+
+ {
+ SdpImageattrAttributeList::Imageattr imageattr(
+ ParseImageattr("8 send [x=320,y=240] recv *"));
+ ASSERT_EQ(8U, *imageattr.pt);
+ ASSERT_FALSE(imageattr.sendAll);
+ ASSERT_EQ(1U, imageattr.sendSets.size());
+ ASSERT_EQ(1U, imageattr.sendSets[0].xRange.discreteValues.size());
+ ASSERT_EQ(320U, imageattr.sendSets[0].xRange.discreteValues[0]);
+ ASSERT_EQ(1U, imageattr.sendSets[0].yRange.discreteValues.size());
+ ASSERT_EQ(240U, imageattr.sendSets[0].yRange.discreteValues[0]);
+ ASSERT_TRUE(imageattr.recvAll);
+ ASSERT_TRUE(imageattr.recvSets.empty());
+ }
+
+ {
+ SdpImageattrAttributeList::Imageattr imageattr(
+ ParseImageattr("8 send [x=320,y=240] [x=640,y=480] recv *"));
+ ASSERT_EQ(8U, *imageattr.pt);
+ ASSERT_FALSE(imageattr.sendAll);
+ ASSERT_EQ(2U, imageattr.sendSets.size());
+ ASSERT_EQ(1U, imageattr.sendSets[0].xRange.discreteValues.size());
+ ASSERT_EQ(320U, imageattr.sendSets[0].xRange.discreteValues[0]);
+ ASSERT_EQ(1U, imageattr.sendSets[0].yRange.discreteValues.size());
+ ASSERT_EQ(240U, imageattr.sendSets[0].yRange.discreteValues[0]);
+ ASSERT_EQ(1U, imageattr.sendSets[1].xRange.discreteValues.size());
+ ASSERT_EQ(640U, imageattr.sendSets[1].xRange.discreteValues[0]);
+ ASSERT_EQ(1U, imageattr.sendSets[1].yRange.discreteValues.size());
+ ASSERT_EQ(480U, imageattr.sendSets[1].yRange.discreteValues[0]);
+ ASSERT_TRUE(imageattr.recvAll);
+ ASSERT_TRUE(imageattr.recvSets.empty());
+ }
+
+ {
+ SdpImageattrAttributeList::Imageattr imageattr(
+ ParseImageattr("8 send * recv [x=320,y=240]"));
+ ASSERT_EQ(8U, *imageattr.pt);
+ ASSERT_FALSE(imageattr.recvAll);
+ ASSERT_EQ(1U, imageattr.recvSets.size());
+ ASSERT_EQ(1U, imageattr.recvSets[0].xRange.discreteValues.size());
+ ASSERT_EQ(320U, imageattr.recvSets[0].xRange.discreteValues[0]);
+ ASSERT_EQ(1U, imageattr.recvSets[0].yRange.discreteValues.size());
+ ASSERT_EQ(240U, imageattr.recvSets[0].yRange.discreteValues[0]);
+ ASSERT_TRUE(imageattr.sendAll);
+ ASSERT_TRUE(imageattr.sendSets.empty());
+ }
+
+ {
+ SdpImageattrAttributeList::Imageattr imageattr(
+ ParseImageattr("8 send * recv [x=320,y=240] [x=640,y=480]"));
+ ASSERT_EQ(8U, *imageattr.pt);
+ ASSERT_FALSE(imageattr.recvAll);
+ ASSERT_EQ(2U, imageattr.recvSets.size());
+ ASSERT_EQ(1U, imageattr.recvSets[0].xRange.discreteValues.size());
+ ASSERT_EQ(320U, imageattr.recvSets[0].xRange.discreteValues[0]);
+ ASSERT_EQ(1U, imageattr.recvSets[0].yRange.discreteValues.size());
+ ASSERT_EQ(240U, imageattr.recvSets[0].yRange.discreteValues[0]);
+ ASSERT_EQ(1U, imageattr.recvSets[1].xRange.discreteValues.size());
+ ASSERT_EQ(640U, imageattr.recvSets[1].xRange.discreteValues[0]);
+ ASSERT_EQ(1U, imageattr.recvSets[1].yRange.discreteValues.size());
+ ASSERT_EQ(480U, imageattr.recvSets[1].yRange.discreteValues[0]);
+ ASSERT_TRUE(imageattr.sendAll);
+ ASSERT_TRUE(imageattr.sendSets.empty());
+ }
+}
+
+TEST(NewSdpTestNoFixture, CheckImageattrParseInvalid)
+{
+ ParseInvalid<SdpImageattrAttributeList::Imageattr>("", 0);
+ ParseInvalid<SdpImageattrAttributeList::Imageattr>(" ", 0);
+ ParseInvalid<SdpImageattrAttributeList::Imageattr>("-1", 0);
+ ParseInvalid<SdpImageattrAttributeList::Imageattr>("99999 ", 5);
+ ParseInvalid<SdpImageattrAttributeList::Imageattr>("*", 1);
+ ParseInvalid<SdpImageattrAttributeList::Imageattr>("* sen", 5);
+ ParseInvalid<SdpImageattrAttributeList::Imageattr>("* vcer *", 6);
+ ParseInvalid<SdpImageattrAttributeList::Imageattr>("* send x", 7);
+ ParseInvalid<SdpImageattrAttributeList::Imageattr>(
+ "* send [x=640,y=480] [", 22);
+ ParseInvalid<SdpImageattrAttributeList::Imageattr>("* send * sen", 12);
+ ParseInvalid<SdpImageattrAttributeList::Imageattr>("* send * vcer *", 13);
+ ParseInvalid<SdpImageattrAttributeList::Imageattr>("* send * send *", 13);
+ ParseInvalid<SdpImageattrAttributeList::Imageattr>("* recv * recv *", 13);
+ ParseInvalid<SdpImageattrAttributeList::Imageattr>("* send * recv x", 14);
+ ParseInvalid<SdpImageattrAttributeList::Imageattr>(
+ "* send * recv [x=640,y=480] [", 29);
+ ParseInvalid<SdpImageattrAttributeList::Imageattr>(
+ "* send * recv [x=640,y=480] *", 28);
+ ParseInvalid<SdpImageattrAttributeList::Imageattr>(
+ "* send * recv [x=640,y=480] foobajooba", 28);
+}
+
+TEST(NewSdpTestNoFixture, CheckImageattrXYRangeSerialization)
+{
+ SdpImageattrAttributeList::XYRange range;
+ std::stringstream os;
+
+ range.min = 320;
+ range.max = 640;
+ range.Serialize(os);
+ ASSERT_EQ("[320:640]", os.str());
+ os.str(""); // clear
+
+ range.step = 16;
+ range.Serialize(os);
+ ASSERT_EQ("[320:16:640]", os.str());
+ os.str(""); // clear
+
+ range.min = 0;
+ range.max = 0;
+ range.discreteValues.push_back(320);
+ range.Serialize(os);
+ ASSERT_EQ("320", os.str());
+ os.str("");
+
+ range.discreteValues.push_back(640);
+ range.Serialize(os);
+ ASSERT_EQ("[320,640]", os.str());
+}
+
+TEST(NewSdpTestNoFixture, CheckImageattrSRangeSerialization)
+{
+ SdpImageattrAttributeList::SRange range;
+ std::ostringstream os;
+
+ range.min = 0.1f;
+ range.max = 0.9999f;
+ range.Serialize(os);
+ ASSERT_EQ("[0.1000-0.9999]", os.str());
+ os.str("");
+
+ range.min = 0.0f;
+ range.max = 0.0f;
+ range.discreteValues.push_back(0.1f);
+ range.Serialize(os);
+ ASSERT_EQ("0.1000", os.str());
+ os.str("");
+
+ range.discreteValues.push_back(0.5f);
+ range.Serialize(os);
+ ASSERT_EQ("[0.1000,0.5000]", os.str());
+}
+
+TEST(NewSdpTestNoFixture, CheckImageattrPRangeSerialization)
+{
+ SdpImageattrAttributeList::PRange range;
+ std::ostringstream os;
+
+ range.min = 0.1f;
+ range.max = 0.9999f;
+ range.Serialize(os);
+ ASSERT_EQ("[0.1000-0.9999]", os.str());
+}
+
+TEST(NewSdpTestNoFixture, CheckImageattrSetSerialization)
+{
+ SdpImageattrAttributeList::Set set;
+ std::ostringstream os;
+
+ set.xRange.discreteValues.push_back(640);
+ set.yRange.discreteValues.push_back(480);
+ set.Serialize(os);
+ ASSERT_EQ("[x=640,y=480]", os.str());
+ os.str("");
+
+ set.qValue = 0.00f;
+ set.Serialize(os);
+ ASSERT_EQ("[x=640,y=480,q=0.00]", os.str());
+ os.str("");
+
+ set.qValue = 0.10f;
+ set.Serialize(os);
+ ASSERT_EQ("[x=640,y=480,q=0.10]", os.str());
+ os.str("");
+
+ set.qValue = 1.00f;
+ set.Serialize(os);
+ ASSERT_EQ("[x=640,y=480,q=1.00]", os.str());
+ os.str("");
+
+ set.sRange.discreteValues.push_back(1.1f);
+ set.Serialize(os);
+ ASSERT_EQ("[x=640,y=480,sar=1.1000,q=1.00]", os.str());
+ os.str("");
+
+ set.pRange.min = 0.9f;
+ set.pRange.max = 1.1f;
+ set.Serialize(os);
+ ASSERT_EQ("[x=640,y=480,sar=1.1000,par=[0.9000-1.1000],q=1.00]", os.str());
+ os.str("");
+}
+
+TEST(NewSdpTestNoFixture, CheckImageattrSerialization)
+{
+ SdpImageattrAttributeList::Imageattr imageattr;
+ std::ostringstream os;
+
+ imageattr.sendAll = true;
+ imageattr.pt = Some<uint16_t>(8U);
+ imageattr.Serialize(os);
+ ASSERT_EQ("8 send *", os.str());
+ os.str("");
+
+ imageattr.pt.reset();;
+ imageattr.Serialize(os);
+ ASSERT_EQ("* send *", os.str());
+ os.str("");
+
+ imageattr.sendAll = false;
+ imageattr.recvAll = true;
+ imageattr.Serialize(os);
+ ASSERT_EQ("* recv *", os.str());
+ os.str("");
+
+ imageattr.sendAll = true;
+ imageattr.Serialize(os);
+ ASSERT_EQ("* send * recv *", os.str());
+ os.str("");
+
+ imageattr.sendAll = false;
+ imageattr.sendSets.push_back(SdpImageattrAttributeList::Set());
+ imageattr.sendSets.back().xRange.discreteValues.push_back(320);
+ imageattr.sendSets.back().yRange.discreteValues.push_back(240);
+ imageattr.Serialize(os);
+ ASSERT_EQ("* send [x=320,y=240] recv *", os.str());
+ os.str("");
+
+ imageattr.sendSets.push_back(SdpImageattrAttributeList::Set());
+ imageattr.sendSets.back().xRange.discreteValues.push_back(640);
+ imageattr.sendSets.back().yRange.discreteValues.push_back(480);
+ imageattr.Serialize(os);
+ ASSERT_EQ("* send [x=320,y=240] [x=640,y=480] recv *", os.str());
+ os.str("");
+
+ imageattr.recvAll = false;
+ imageattr.recvSets.push_back(SdpImageattrAttributeList::Set());
+ imageattr.recvSets.back().xRange.discreteValues.push_back(320);
+ imageattr.recvSets.back().yRange.discreteValues.push_back(240);
+ imageattr.Serialize(os);
+ ASSERT_EQ("* send [x=320,y=240] [x=640,y=480] recv [x=320,y=240]", os.str());
+ os.str("");
+
+ imageattr.recvSets.push_back(SdpImageattrAttributeList::Set());
+ imageattr.recvSets.back().xRange.discreteValues.push_back(640);
+ imageattr.recvSets.back().yRange.discreteValues.push_back(480);
+ imageattr.Serialize(os);
+ ASSERT_EQ(
+ "* send [x=320,y=240] [x=640,y=480] recv [x=320,y=240] [x=640,y=480]",
+ os.str());
+ os.str("");
+}
+
+TEST(NewSdpTestNoFixture, CheckSimulcastVersionSerialize)
+{
+ std::ostringstream os;
+
+ SdpSimulcastAttribute::Version version;
+ version.choices.push_back("8");
+ version.Serialize(os);
+ ASSERT_EQ("8", os.str());
+ os.str("");
+
+ version.choices.push_back("9");
+ version.Serialize(os);
+ ASSERT_EQ("8,9", os.str());
+ os.str("");
+
+ version.choices.push_back("0");
+ version.Serialize(os);
+ ASSERT_EQ("8,9,0", os.str());
+ os.str("");
+}
+
+static SdpSimulcastAttribute::Version
+ParseSimulcastVersion(const std::string& input)
+{
+ std::istringstream is(input + ";");
+ std::string error;
+ SdpSimulcastAttribute::Version version;
+ EXPECT_TRUE(version.Parse(is, &error)) << error;
+ EXPECT_EQ(';', is.get());
+ EXPECT_EQ(EOF, is.get());
+ return version;
+}
+
+TEST(NewSdpTestNoFixture, CheckSimulcastVersionValidParse)
+{
+ {
+ SdpSimulcastAttribute::Version version(
+ ParseSimulcastVersion("1"));
+ ASSERT_EQ(1U, version.choices.size());
+ ASSERT_EQ("1", version.choices[0]);
+ }
+
+ {
+ SdpSimulcastAttribute::Version version(
+ ParseSimulcastVersion("1,2"));
+ ASSERT_EQ(2U, version.choices.size());
+ ASSERT_EQ("1", version.choices[0]);
+ ASSERT_EQ("2", version.choices[1]);
+ }
+}
+
+TEST(NewSdpTestNoFixture, CheckSimulcastVersionInvalidParse)
+{
+ ParseInvalid<SdpSimulcastAttribute::Version>("", 0);
+ ParseInvalid<SdpSimulcastAttribute::Version>(",", 0);
+ ParseInvalid<SdpSimulcastAttribute::Version>(";", 0);
+ ParseInvalid<SdpSimulcastAttribute::Version>(" ", 0);
+ ParseInvalid<SdpSimulcastAttribute::Version>("8,", 2);
+ ParseInvalid<SdpSimulcastAttribute::Version>("8, ", 2);
+ ParseInvalid<SdpSimulcastAttribute::Version>("8,,", 2);
+ ParseInvalid<SdpSimulcastAttribute::Version>("8,;", 2);
+}
+
+TEST(NewSdpTestNoFixture, CheckSimulcastVersionsSerialize)
+{
+ std::ostringstream os;
+
+ SdpSimulcastAttribute::Versions versions;
+ versions.type = SdpSimulcastAttribute::Versions::kPt;
+ versions.push_back(SdpSimulcastAttribute::Version());
+ versions.back().choices.push_back("8");
+ versions.Serialize(os);
+ ASSERT_EQ("pt=8", os.str());
+ os.str("");
+
+ versions.type = SdpSimulcastAttribute::Versions::kRid;
+ versions.Serialize(os);
+ ASSERT_EQ("rid=8", os.str());
+ os.str("");
+
+ versions.push_back(SdpSimulcastAttribute::Version());
+ versions.Serialize(os);
+ ASSERT_EQ("rid=8", os.str());
+ os.str("");
+
+ versions.back().choices.push_back("9");
+ versions.Serialize(os);
+ ASSERT_EQ("rid=8;9", os.str());
+ os.str("");
+
+ versions.push_back(SdpSimulcastAttribute::Version());
+ versions.back().choices.push_back("0");
+ versions.Serialize(os);
+ ASSERT_EQ("rid=8;9;0", os.str());
+ os.str("");
+}
+
+static SdpSimulcastAttribute::Versions
+ParseSimulcastVersions(const std::string& input)
+{
+ std::istringstream is(input + " ");
+ std::string error;
+ SdpSimulcastAttribute::Versions list;
+ EXPECT_TRUE(list.Parse(is, &error)) << error;
+ EXPECT_EQ(' ', is.get());
+ EXPECT_EQ(EOF, is.get());
+ return list;
+}
+
+TEST(NewSdpTestNoFixture, CheckSimulcastVersionsValidParse)
+{
+ {
+ SdpSimulcastAttribute::Versions versions(
+ ParseSimulcastVersions("pt=8"));
+ ASSERT_EQ(1U, versions.size());
+ ASSERT_EQ(SdpSimulcastAttribute::Versions::kPt, versions.type);
+ ASSERT_EQ(1U, versions[0].choices.size());
+ ASSERT_EQ("8", versions[0].choices[0]);
+ }
+
+ {
+ SdpSimulcastAttribute::Versions versions(
+ ParseSimulcastVersions("rid=8"));
+ ASSERT_EQ(1U, versions.size());
+ ASSERT_EQ(SdpSimulcastAttribute::Versions::kRid, versions.type);
+ ASSERT_EQ(1U, versions[0].choices.size());
+ ASSERT_EQ("8", versions[0].choices[0]);
+ }
+
+ {
+ SdpSimulcastAttribute::Versions versions(
+ ParseSimulcastVersions("pt=8,9"));
+ ASSERT_EQ(1U, versions.size());
+ ASSERT_EQ(2U, versions[0].choices.size());
+ ASSERT_EQ("8", versions[0].choices[0]);
+ ASSERT_EQ("9", versions[0].choices[1]);
+ }
+
+ {
+ SdpSimulcastAttribute::Versions versions(
+ ParseSimulcastVersions("pt=8,9;10"));
+ ASSERT_EQ(2U, versions.size());
+ ASSERT_EQ(2U, versions[0].choices.size());
+ ASSERT_EQ("8", versions[0].choices[0]);
+ ASSERT_EQ("9", versions[0].choices[1]);
+ ASSERT_EQ(1U, versions[1].choices.size());
+ ASSERT_EQ("10", versions[1].choices[0]);
+ }
+}
+
+TEST(NewSdpTestNoFixture, CheckSimulcastVersionsInvalidParse)
+{
+ ParseInvalid<SdpSimulcastAttribute::Versions>("", 0);
+ ParseInvalid<SdpSimulcastAttribute::Versions>("x", 1);
+ ParseInvalid<SdpSimulcastAttribute::Versions>(";", 1);
+ ParseInvalid<SdpSimulcastAttribute::Versions>("8", 1);
+ ParseInvalid<SdpSimulcastAttribute::Versions>("foo=", 4);
+ ParseInvalid<SdpSimulcastAttribute::Versions>("foo=8", 4);
+ ParseInvalid<SdpSimulcastAttribute::Versions>("pt=9999", 7);
+ ParseInvalid<SdpSimulcastAttribute::Versions>("pt=-1", 5);
+ ParseInvalid<SdpSimulcastAttribute::Versions>("pt=x", 4);
+ ParseInvalid<SdpSimulcastAttribute::Versions>("pt=8;", 5);
+ ParseInvalid<SdpSimulcastAttribute::Versions>("pt=8;x", 6);
+ ParseInvalid<SdpSimulcastAttribute::Versions>("pt=8;;", 5);
+}
+
+TEST(NewSdpTestNoFixture, CheckSimulcastSerialize)
+{
+ std::ostringstream os;
+
+ SdpSimulcastAttribute simulcast;
+ simulcast.recvVersions.type = SdpSimulcastAttribute::Versions::kPt;
+ simulcast.recvVersions.push_back(SdpSimulcastAttribute::Version());
+ simulcast.recvVersions.back().choices.push_back("8");
+ simulcast.Serialize(os);
+ ASSERT_EQ("a=simulcast: recv pt=8" CRLF, os.str());
+ os.str("");
+
+ simulcast.sendVersions.push_back(SdpSimulcastAttribute::Version());
+ simulcast.sendVersions.back().choices.push_back("9");
+ simulcast.Serialize(os);
+ ASSERT_EQ("a=simulcast: send rid=9 recv pt=8" CRLF, os.str());
+ os.str("");
+}
+
+static SdpSimulcastAttribute
+ParseSimulcast(const std::string& input)
+{
+ std::istringstream is(input);
+ std::string error;
+ SdpSimulcastAttribute simulcast;
+ EXPECT_TRUE(simulcast.Parse(is, &error)) << error;
+ EXPECT_TRUE(is.eof());
+ return simulcast;
+}
+
+TEST(NewSdpTestNoFixture, CheckSimulcastValidParse)
+{
+ {
+ SdpSimulcastAttribute simulcast(ParseSimulcast(" send pt=8"));
+ ASSERT_EQ(1U, simulcast.sendVersions.size());
+ ASSERT_EQ(SdpSimulcastAttribute::Versions::kPt,
+ simulcast.sendVersions.type);
+ ASSERT_EQ(1U, simulcast.sendVersions[0].choices.size());
+ ASSERT_EQ("8", simulcast.sendVersions[0].choices[0]);
+ ASSERT_EQ(0U, simulcast.recvVersions.size());
+ }
+
+ {
+ SdpSimulcastAttribute simulcast(ParseSimulcast(" SEND pt=8"));
+ ASSERT_EQ(1U, simulcast.sendVersions.size());
+ ASSERT_EQ(SdpSimulcastAttribute::Versions::kPt,
+ simulcast.sendVersions.type);
+ ASSERT_EQ(1U, simulcast.sendVersions[0].choices.size());
+ ASSERT_EQ("8", simulcast.sendVersions[0].choices[0]);
+ ASSERT_EQ(0U, simulcast.recvVersions.size());
+ }
+
+ {
+ SdpSimulcastAttribute simulcast(ParseSimulcast(" recv pt=8"));
+ ASSERT_EQ(1U, simulcast.recvVersions.size());
+ ASSERT_EQ(SdpSimulcastAttribute::Versions::kPt,
+ simulcast.recvVersions.type);
+ ASSERT_EQ(1U, simulcast.recvVersions[0].choices.size());
+ ASSERT_EQ("8", simulcast.recvVersions[0].choices[0]);
+ ASSERT_EQ(0U, simulcast.sendVersions.size());
+ }
+
+ {
+ SdpSimulcastAttribute simulcast(
+ ParseSimulcast(
+ " send pt=8,9;101;97,98 recv pt=101,120;97"));
+ ASSERT_EQ(3U, simulcast.sendVersions.size());
+ ASSERT_EQ(SdpSimulcastAttribute::Versions::kPt,
+ simulcast.sendVersions.type);
+ ASSERT_EQ(2U, simulcast.sendVersions[0].choices.size());
+ ASSERT_EQ("8", simulcast.sendVersions[0].choices[0]);
+ ASSERT_EQ("9", simulcast.sendVersions[0].choices[1]);
+ ASSERT_EQ(1U, simulcast.sendVersions[1].choices.size());
+ ASSERT_EQ("101", simulcast.sendVersions[1].choices[0]);
+ ASSERT_EQ(2U, simulcast.sendVersions[2].choices.size());
+ ASSERT_EQ("97", simulcast.sendVersions[2].choices[0]);
+ ASSERT_EQ("98", simulcast.sendVersions[2].choices[1]);
+
+ ASSERT_EQ(2U, simulcast.recvVersions.size());
+ ASSERT_EQ(SdpSimulcastAttribute::Versions::kPt,
+ simulcast.recvVersions.type);
+ ASSERT_EQ(2U, simulcast.recvVersions[0].choices.size());
+ ASSERT_EQ("101", simulcast.recvVersions[0].choices[0]);
+ ASSERT_EQ("120", simulcast.recvVersions[0].choices[1]);
+ ASSERT_EQ(1U, simulcast.recvVersions[1].choices.size());
+ ASSERT_EQ("97", simulcast.recvVersions[1].choices[0]);
+ }
+}
+
+TEST(NewSdpTestNoFixture, CheckSimulcastInvalidParse)
+{
+ ParseInvalid<SdpSimulcastAttribute>("", 0);
+ ParseInvalid<SdpSimulcastAttribute>(" ", 1);
+ ParseInvalid<SdpSimulcastAttribute>("vcer ", 4);
+ ParseInvalid<SdpSimulcastAttribute>(" send x", 7);
+ ParseInvalid<SdpSimulcastAttribute>(" recv x", 7);
+ ParseInvalid<SdpSimulcastAttribute>(" send pt=8 send ", 15);
+ ParseInvalid<SdpSimulcastAttribute>(" recv pt=8 recv ", 15);
+}
+
+static SdpRidAttributeList::Rid
+ParseRid(const std::string& input)
+{
+ std::istringstream is(input);
+ std::string error;
+ SdpRidAttributeList::Rid rid;
+ EXPECT_TRUE(rid.Parse(is, &error)) << error;
+ EXPECT_TRUE(is.eof());
+ return rid;
+}
+
+TEST(NewSdpTestNoFixture, CheckRidValidParse)
+{
+ {
+ SdpRidAttributeList::Rid rid(ParseRid("1 send"));
+ ASSERT_EQ("1", rid.id);
+ ASSERT_EQ(sdp::kSend, rid.direction);
+ ASSERT_EQ(0U, rid.formats.size());
+ ASSERT_EQ(0U, rid.constraints.maxWidth);
+ ASSERT_EQ(0U, rid.constraints.maxHeight);
+ ASSERT_EQ(0U, rid.constraints.maxFps);
+ ASSERT_EQ(0U, rid.constraints.maxFs);
+ ASSERT_EQ(0U, rid.constraints.maxBr);
+ ASSERT_EQ(0U, rid.constraints.maxPps);
+ ASSERT_EQ(0U, rid.dependIds.size());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid(ParseRid("1 send pt=96;max-width=800"));
+ ASSERT_EQ("1", rid.id);
+ ASSERT_EQ(sdp::kSend, rid.direction);
+ ASSERT_EQ(1U, rid.formats.size());
+ ASSERT_EQ(96U, rid.formats[0]);
+ ASSERT_EQ(800U, rid.constraints.maxWidth);
+ ASSERT_EQ(0U, rid.constraints.maxHeight);
+ ASSERT_EQ(0U, rid.constraints.maxFps);
+ ASSERT_EQ(0U, rid.constraints.maxFs);
+ ASSERT_EQ(0U, rid.constraints.maxBr);
+ ASSERT_EQ(0U, rid.constraints.maxPps);
+ ASSERT_EQ(0U, rid.dependIds.size());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid(ParseRid("1 send pt=96,97,98;max-width=800"));
+ ASSERT_EQ("1", rid.id);
+ ASSERT_EQ(sdp::kSend, rid.direction);
+ ASSERT_EQ(3U, rid.formats.size());
+ ASSERT_EQ(96U, rid.formats[0]);
+ ASSERT_EQ(97U, rid.formats[1]);
+ ASSERT_EQ(98U, rid.formats[2]);
+ ASSERT_EQ(800U, rid.constraints.maxWidth);
+ ASSERT_EQ(0U, rid.constraints.maxHeight);
+ ASSERT_EQ(0U, rid.constraints.maxFps);
+ ASSERT_EQ(0U, rid.constraints.maxFs);
+ ASSERT_EQ(0U, rid.constraints.maxBr);
+ ASSERT_EQ(0U, rid.constraints.maxPps);
+ ASSERT_EQ(0U, rid.dependIds.size());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid(
+ ParseRid("0123456789az-_ recv max-width=800"));
+ ASSERT_EQ("0123456789az-_", rid.id);
+ ASSERT_EQ(sdp::kRecv, rid.direction);
+ ASSERT_EQ(0U, rid.formats.size());
+ ASSERT_EQ(800U, rid.constraints.maxWidth);
+ ASSERT_EQ(0U, rid.constraints.maxHeight);
+ ASSERT_EQ(0U, rid.constraints.maxFps);
+ ASSERT_EQ(0U, rid.constraints.maxFs);
+ ASSERT_EQ(0U, rid.constraints.maxBr);
+ ASSERT_EQ(0U, rid.constraints.maxPps);
+ ASSERT_EQ(0U, rid.dependIds.size());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid(
+ ParseRid("foo send"));
+ ASSERT_EQ(0U, rid.formats.size());
+ ASSERT_EQ(0U, rid.constraints.maxWidth);
+ ASSERT_EQ(0U, rid.constraints.maxHeight);
+ ASSERT_EQ(0U, rid.constraints.maxFps);
+ ASSERT_EQ(0U, rid.constraints.maxFs);
+ ASSERT_EQ(0U, rid.constraints.maxBr);
+ ASSERT_EQ(0U, rid.constraints.maxPps);
+ ASSERT_EQ(0U, rid.dependIds.size());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid(
+ ParseRid("foo send pt=96"));
+ ASSERT_EQ(1U, rid.formats.size());
+ ASSERT_EQ(96U, rid.formats[0]);
+ ASSERT_EQ(0U, rid.constraints.maxWidth);
+ ASSERT_EQ(0U, rid.constraints.maxHeight);
+ ASSERT_EQ(0U, rid.constraints.maxFps);
+ ASSERT_EQ(0U, rid.constraints.maxFs);
+ ASSERT_EQ(0U, rid.constraints.maxBr);
+ ASSERT_EQ(0U, rid.constraints.maxPps);
+ ASSERT_EQ(0U, rid.dependIds.size());
+ }
+
+ // This is not technically permitted by the BNF, but the parse code is simpler
+ // if we allow it. If we decide to stop allowing this, this will need to be
+ // converted to an invalid parse test-case.
+ {
+ SdpRidAttributeList::Rid rid(
+ ParseRid("foo send max-br=30000;pt=96"));
+ ASSERT_EQ(1U, rid.formats.size());
+ ASSERT_EQ(96U, rid.formats[0]);
+ ASSERT_EQ(0U, rid.constraints.maxWidth);
+ ASSERT_EQ(0U, rid.constraints.maxHeight);
+ ASSERT_EQ(0U, rid.constraints.maxFps);
+ ASSERT_EQ(0U, rid.constraints.maxFs);
+ ASSERT_EQ(30000U, rid.constraints.maxBr);
+ ASSERT_EQ(0U, rid.constraints.maxPps);
+ ASSERT_EQ(0U, rid.dependIds.size());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid(
+ ParseRid("foo send pt=96,97,98"));
+ ASSERT_EQ(3U, rid.formats.size());
+ ASSERT_EQ(96U, rid.formats[0]);
+ ASSERT_EQ(97U, rid.formats[1]);
+ ASSERT_EQ(98U, rid.formats[2]);
+ ASSERT_EQ(0U, rid.constraints.maxWidth);
+ ASSERT_EQ(0U, rid.constraints.maxHeight);
+ ASSERT_EQ(0U, rid.constraints.maxFps);
+ ASSERT_EQ(0U, rid.constraints.maxFs);
+ ASSERT_EQ(0U, rid.constraints.maxBr);
+ ASSERT_EQ(0U, rid.constraints.maxPps);
+ ASSERT_EQ(0U, rid.dependIds.size());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid(
+ ParseRid("foo send max-width=800"));
+ ASSERT_EQ(0U, rid.formats.size());
+ ASSERT_EQ(800U, rid.constraints.maxWidth);
+ ASSERT_EQ(0U, rid.constraints.maxHeight);
+ ASSERT_EQ(0U, rid.constraints.maxFps);
+ ASSERT_EQ(0U, rid.constraints.maxFs);
+ ASSERT_EQ(0U, rid.constraints.maxBr);
+ ASSERT_EQ(0U, rid.constraints.maxPps);
+ ASSERT_EQ(0U, rid.dependIds.size());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid(
+ ParseRid("foo send max-height=640"));
+ ASSERT_EQ(0U, rid.formats.size());
+ ASSERT_EQ(0U, rid.constraints.maxWidth);
+ ASSERT_EQ(640U, rid.constraints.maxHeight);
+ ASSERT_EQ(0U, rid.constraints.maxFps);
+ ASSERT_EQ(0U, rid.constraints.maxFs);
+ ASSERT_EQ(0U, rid.constraints.maxBr);
+ ASSERT_EQ(0U, rid.constraints.maxPps);
+ ASSERT_EQ(0U, rid.dependIds.size());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid(
+ ParseRid("foo send max-fps=30"));
+ ASSERT_EQ(0U, rid.formats.size());
+ ASSERT_EQ(0U, rid.constraints.maxWidth);
+ ASSERT_EQ(0U, rid.constraints.maxHeight);
+ ASSERT_EQ(30U, rid.constraints.maxFps);
+ ASSERT_EQ(0U, rid.constraints.maxFs);
+ ASSERT_EQ(0U, rid.constraints.maxBr);
+ ASSERT_EQ(0U, rid.constraints.maxPps);
+ ASSERT_EQ(0U, rid.dependIds.size());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid(
+ ParseRid("foo send max-fs=3600"));
+ ASSERT_EQ(0U, rid.formats.size());
+ ASSERT_EQ(0U, rid.constraints.maxWidth);
+ ASSERT_EQ(0U, rid.constraints.maxHeight);
+ ASSERT_EQ(0U, rid.constraints.maxFps);
+ ASSERT_EQ(3600U, rid.constraints.maxFs);
+ ASSERT_EQ(0U, rid.constraints.maxBr);
+ ASSERT_EQ(0U, rid.constraints.maxPps);
+ ASSERT_EQ(0U, rid.dependIds.size());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid(
+ ParseRid("foo send max-br=30000"));
+ ASSERT_EQ(0U, rid.formats.size());
+ ASSERT_EQ(0U, rid.constraints.maxWidth);
+ ASSERT_EQ(0U, rid.constraints.maxHeight);
+ ASSERT_EQ(0U, rid.constraints.maxFps);
+ ASSERT_EQ(0U, rid.constraints.maxFs);
+ ASSERT_EQ(30000U, rid.constraints.maxBr);
+ ASSERT_EQ(0U, rid.constraints.maxPps);
+ ASSERT_EQ(0U, rid.dependIds.size());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid(
+ ParseRid("foo send max-pps=9216000"));
+ ASSERT_EQ(0U, rid.formats.size());
+ ASSERT_EQ(0U, rid.constraints.maxWidth);
+ ASSERT_EQ(0U, rid.constraints.maxHeight);
+ ASSERT_EQ(0U, rid.constraints.maxFps);
+ ASSERT_EQ(0U, rid.constraints.maxFs);
+ ASSERT_EQ(0U, rid.constraints.maxBr);
+ ASSERT_EQ(9216000U, rid.constraints.maxPps);
+ ASSERT_EQ(0U, rid.dependIds.size());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid(
+ ParseRid("foo send depend=foo"));
+ ASSERT_EQ(0U, rid.formats.size());
+ ASSERT_EQ(0U, rid.constraints.maxWidth);
+ ASSERT_EQ(0U, rid.constraints.maxHeight);
+ ASSERT_EQ(0U, rid.constraints.maxFps);
+ ASSERT_EQ(0U, rid.constraints.maxFs);
+ ASSERT_EQ(0U, rid.constraints.maxBr);
+ ASSERT_EQ(0U, rid.constraints.maxPps);
+ ASSERT_EQ(1U, rid.dependIds.size());
+ ASSERT_EQ("foo", rid.dependIds[0]);
+ }
+
+ {
+ SdpRidAttributeList::Rid rid(
+ ParseRid("foo send max-foo=20"));
+ ASSERT_EQ(0U, rid.formats.size());
+ ASSERT_EQ(0U, rid.constraints.maxWidth);
+ ASSERT_EQ(0U, rid.constraints.maxHeight);
+ ASSERT_EQ(0U, rid.constraints.maxFps);
+ ASSERT_EQ(0U, rid.constraints.maxFs);
+ ASSERT_EQ(0U, rid.constraints.maxBr);
+ ASSERT_EQ(0U, rid.constraints.maxPps);
+ ASSERT_EQ(0U, rid.dependIds.size());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid(
+ ParseRid("foo send depend=foo,bar"));
+ ASSERT_EQ(0U, rid.formats.size());
+ ASSERT_EQ(0U, rid.constraints.maxWidth);
+ ASSERT_EQ(0U, rid.constraints.maxHeight);
+ ASSERT_EQ(0U, rid.constraints.maxFps);
+ ASSERT_EQ(0U, rid.constraints.maxFs);
+ ASSERT_EQ(0U, rid.constraints.maxBr);
+ ASSERT_EQ(0U, rid.constraints.maxPps);
+ ASSERT_EQ(2U, rid.dependIds.size());
+ ASSERT_EQ("foo", rid.dependIds[0]);
+ ASSERT_EQ("bar", rid.dependIds[1]);
+ }
+
+ {
+ SdpRidAttributeList::Rid rid(
+ ParseRid("foo send max-width=800;max-height=600"));
+ ASSERT_EQ(0U, rid.formats.size());
+ ASSERT_EQ(800U, rid.constraints.maxWidth);
+ ASSERT_EQ(600U, rid.constraints.maxHeight);
+ ASSERT_EQ(0U, rid.constraints.maxFps);
+ ASSERT_EQ(0U, rid.constraints.maxFs);
+ ASSERT_EQ(0U, rid.constraints.maxBr);
+ ASSERT_EQ(0U, rid.constraints.maxPps);
+ ASSERT_EQ(0U, rid.dependIds.size());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid(
+ ParseRid("foo send pt=96,97;max-width=800;max-height=600"));
+ ASSERT_EQ(2U, rid.formats.size());
+ ASSERT_EQ(96U, rid.formats[0]);
+ ASSERT_EQ(97U, rid.formats[1]);
+ ASSERT_EQ(800U, rid.constraints.maxWidth);
+ ASSERT_EQ(600U, rid.constraints.maxHeight);
+ ASSERT_EQ(0U, rid.constraints.maxFps);
+ ASSERT_EQ(0U, rid.constraints.maxFs);
+ ASSERT_EQ(0U, rid.constraints.maxBr);
+ ASSERT_EQ(0U, rid.constraints.maxPps);
+ ASSERT_EQ(0U, rid.dependIds.size());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid(
+ ParseRid("foo send depend=foo,bar;max-width=800;max-height=600"));
+ ASSERT_EQ(0U, rid.formats.size());
+ ASSERT_EQ(800U, rid.constraints.maxWidth);
+ ASSERT_EQ(600U, rid.constraints.maxHeight);
+ ASSERT_EQ(0U, rid.constraints.maxFps);
+ ASSERT_EQ(0U, rid.constraints.maxFs);
+ ASSERT_EQ(0U, rid.constraints.maxBr);
+ ASSERT_EQ(0U, rid.constraints.maxPps);
+ ASSERT_EQ(2U, rid.dependIds.size());
+ ASSERT_EQ("foo", rid.dependIds[0]);
+ ASSERT_EQ("bar", rid.dependIds[1]);
+ }
+
+ {
+ SdpRidAttributeList::Rid rid(
+ ParseRid("foo send max-foo=20;max-width=800;max-height=600"));
+ ASSERT_EQ(0U, rid.formats.size());
+ ASSERT_EQ(800U, rid.constraints.maxWidth);
+ ASSERT_EQ(600U, rid.constraints.maxHeight);
+ ASSERT_EQ(0U, rid.constraints.maxFps);
+ ASSERT_EQ(0U, rid.constraints.maxFs);
+ ASSERT_EQ(0U, rid.constraints.maxBr);
+ ASSERT_EQ(0U, rid.constraints.maxPps);
+ ASSERT_EQ(0U, rid.dependIds.size());
+ }
+}
+
+TEST(NewSdpTestNoFixture, CheckRidInvalidParse)
+{
+ ParseInvalid<SdpRidAttributeList::Rid>("", 0);
+ ParseInvalid<SdpRidAttributeList::Rid>(" ", 0);
+ ParseInvalid<SdpRidAttributeList::Rid>("foo", 3);
+ ParseInvalid<SdpRidAttributeList::Rid>("foo ", 4);
+ ParseInvalid<SdpRidAttributeList::Rid>("foo ", 5);
+ ParseInvalid<SdpRidAttributeList::Rid>("foo bar", 7);
+ ParseInvalid<SdpRidAttributeList::Rid>("foo recv ", 9);
+ ParseInvalid<SdpRidAttributeList::Rid>("foo recv pt=", 12);
+ ParseInvalid<SdpRidAttributeList::Rid>(" ", 0);
+ ParseInvalid<SdpRidAttributeList::Rid>("foo send pt", 11);
+ ParseInvalid<SdpRidAttributeList::Rid>("foo send pt=", 12);
+ ParseInvalid<SdpRidAttributeList::Rid>("foo send pt=x", 12);
+ ParseInvalid<SdpRidAttributeList::Rid>("foo send pt=-1", 12);
+ ParseInvalid<SdpRidAttributeList::Rid>("foo send pt=96,", 15);
+ ParseInvalid<SdpRidAttributeList::Rid>("foo send pt=196", 15);
+ ParseInvalid<SdpRidAttributeList::Rid>("foo send max-width", 18);
+ ParseInvalid<SdpRidAttributeList::Rid>("foo send max-width=", 19);
+ ParseInvalid<SdpRidAttributeList::Rid>("foo send max-width=x", 19);
+ ParseInvalid<SdpRidAttributeList::Rid>("foo send max-width=-1", 19);
+ ParseInvalid<SdpRidAttributeList::Rid>("foo send max-width=800;", 23);
+ ParseInvalid<SdpRidAttributeList::Rid>("foo send max-width=800; ", 24);
+ ParseInvalid<SdpRidAttributeList::Rid>("foo send depend=",16);
+ ParseInvalid<SdpRidAttributeList::Rid>("foo send depend=,", 16);
+ ParseInvalid<SdpRidAttributeList::Rid>("foo send depend=1,", 18);
+}
+
+TEST(NewSdpTestNoFixture, CheckRidSerialize)
+{
+ {
+ SdpRidAttributeList::Rid rid;
+ rid.id = "foo";
+ rid.direction = sdp::kSend;
+ std::ostringstream os;
+ rid.Serialize(os);
+ ASSERT_EQ("foo send", os.str());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid;
+ rid.id = "foo";
+ rid.direction = sdp::kSend;
+ std::ostringstream os;
+ rid.Serialize(os);
+ ASSERT_EQ("foo send", os.str());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid;
+ rid.id = "foo";
+ rid.direction = sdp::kSend;
+ rid.formats.push_back(96);
+ std::ostringstream os;
+ rid.Serialize(os);
+ ASSERT_EQ("foo send pt=96", os.str());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid;
+ rid.id = "foo";
+ rid.direction = sdp::kSend;
+ rid.formats.push_back(96);
+ rid.formats.push_back(97);
+ std::ostringstream os;
+ rid.Serialize(os);
+ ASSERT_EQ("foo send pt=96,97", os.str());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid;
+ rid.id = "foo";
+ rid.direction = sdp::kSend;
+ rid.constraints.maxWidth = 800;
+ std::ostringstream os;
+ rid.Serialize(os);
+ ASSERT_EQ("foo send max-width=800", os.str());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid;
+ rid.id = "foo";
+ rid.direction = sdp::kSend;
+ rid.constraints.maxHeight = 600;
+ std::ostringstream os;
+ rid.Serialize(os);
+ ASSERT_EQ("foo send max-height=600", os.str());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid;
+ rid.id = "foo";
+ rid.direction = sdp::kSend;
+ rid.constraints.maxFps = 30;
+ std::ostringstream os;
+ rid.Serialize(os);
+ ASSERT_EQ("foo send max-fps=30", os.str());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid;
+ rid.id = "foo";
+ rid.direction = sdp::kSend;
+ rid.constraints.maxFs = 3600;
+ std::ostringstream os;
+ rid.Serialize(os);
+ ASSERT_EQ("foo send max-fs=3600", os.str());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid;
+ rid.id = "foo";
+ rid.direction = sdp::kSend;
+ rid.constraints.maxBr = 30000;
+ std::ostringstream os;
+ rid.Serialize(os);
+ ASSERT_EQ("foo send max-br=30000", os.str());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid;
+ rid.id = "foo";
+ rid.direction = sdp::kSend;
+ rid.constraints.maxPps = 9216000;
+ std::ostringstream os;
+ rid.Serialize(os);
+ ASSERT_EQ("foo send max-pps=9216000", os.str());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid;
+ rid.id = "foo";
+ rid.direction = sdp::kSend;
+ rid.dependIds.push_back("foo");
+ std::ostringstream os;
+ rid.Serialize(os);
+ ASSERT_EQ("foo send depend=foo", os.str());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid;
+ rid.id = "foo";
+ rid.direction = sdp::kSend;
+ rid.dependIds.push_back("foo");
+ rid.dependIds.push_back("bar");
+ std::ostringstream os;
+ rid.Serialize(os);
+ ASSERT_EQ("foo send depend=foo,bar", os.str());
+ }
+
+ {
+ SdpRidAttributeList::Rid rid;
+ rid.id = "foo";
+ rid.direction = sdp::kSend;
+ rid.formats.push_back(96);
+ rid.constraints.maxBr = 30000;
+ std::ostringstream os;
+ rid.Serialize(os);
+ ASSERT_EQ("foo send pt=96;max-br=30000", os.str());
+ }
+}
+
+} // End namespace test.
+
+int main(int argc, char **argv) {
+ ScopedXPCOM xpcom("sdp_unittests");
+
+ test_utils = new MtransportTestUtils();
+ NSS_NoDB_Init(nullptr);
+ NSS_SetDomesticPolicy();
+
+ ::testing::InitGoogleTest(&argc, argv);
+ int result = RUN_ALL_TESTS();
+
+ PeerConnectionCtx::Destroy();
+ delete test_utils;
+
+ return result;
+}
diff --git a/media/webrtc/signaling/test/signaling_unittests.cpp b/media/webrtc/signaling/test/signaling_unittests.cpp
new file mode 100644
index 000000000..27d4750c7
--- /dev/null
+++ b/media/webrtc/signaling/test/signaling_unittests.cpp
@@ -0,0 +1,4851 @@
+/* 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 <algorithm>
+#include <string>
+
+#include "base/basictypes.h"
+#include "logging.h"
+
+#define GTEST_HAS_RTTI 0
+#include "gtest/gtest.h"
+#include "gtest_utils.h"
+
+#include "nspr.h"
+#include "nss.h"
+#include "ssl.h"
+#include "prthread.h"
+
+#include "FakePCObserver.h"
+#include "FakeMediaStreams.h"
+#include "FakeMediaStreamsImpl.h"
+#include "FakeLogging.h"
+#include "PeerConnectionImpl.h"
+#include "PeerConnectionCtx.h"
+#include "PeerConnectionMedia.h"
+#include "MediaPipeline.h"
+#include "runnable_utils.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/Services.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIDNSService.h"
+#include "nsQueryObject.h"
+#include "nsWeakReference.h"
+#include "nricectx.h"
+#include "rlogconnector.h"
+#include "mozilla/SyncRunnable.h"
+#include "logging.h"
+#include "stunserver.h"
+#include "stunserver.cpp"
+#ifdef SIGNALING_UNITTEST_STANDALONE
+#include "PeerConnectionImplEnumsBinding.cpp"
+#endif
+
+#include "FakeIPC.h"
+#include "FakeIPC.cpp"
+
+#include "ice_ctx.h"
+#include "ice_peer_ctx.h"
+
+#include "mtransport_test_utils.h"
+#include "gtest_ringbuffer_dumper.h"
+MtransportTestUtils *test_utils;
+nsCOMPtr<nsIThread> gMainThread;
+nsCOMPtr<nsIThread> gGtestThread;
+bool gTestsComplete = false;
+
+#ifndef USE_FAKE_MEDIA_STREAMS
+#error USE_FAKE_MEDIA_STREAMS undefined
+#endif
+#ifndef USE_FAKE_PCOBSERVER
+#error USE_FAKE_PCOBSERVER undefined
+#endif
+
+static int kDefaultTimeout = 10000;
+
+static std::string callerName = "caller";
+static std::string calleeName = "callee";
+
+#define ARRAY_TO_STL(container, type, array) \
+ (container<type>((array), (array) + PR_ARRAY_SIZE(array)))
+
+#define ARRAY_TO_SET(type, array) ARRAY_TO_STL(std::set, type, array)
+
+std::string g_stun_server_address((char *)"23.21.150.121");
+uint16_t g_stun_server_port(3478);
+std::string kBogusSrflxAddress((char *)"192.0.2.1");
+uint16_t kBogusSrflxPort(1001);
+
+// We can't use webidl bindings here because it uses nsString,
+// so we pass options in using OfferOptions instead
+class OfferOptions : public mozilla::JsepOfferOptions {
+public:
+ void setInt32Option(const char *namePtr, size_t value) {
+ if (!strcmp(namePtr, "OfferToReceiveAudio")) {
+ mOfferToReceiveAudio = mozilla::Some(value);
+ } else if (!strcmp(namePtr, "OfferToReceiveVideo")) {
+ mOfferToReceiveVideo = mozilla::Some(value);
+ }
+ }
+ void setBoolOption(const char* namePtr, bool value) {
+ if (!strcmp(namePtr, "IceRestart")) {
+ mIceRestart = mozilla::Some(value);
+ }
+ }
+private:
+};
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+// XXX Workaround for bug 998092 to maintain the existing broken semantics
+template<>
+struct nsISupportsWeakReference::COMTypeInfo<nsSupportsWeakReference, void> {
+ static const nsIID kIID;
+};
+//const nsIID nsISupportsWeakReference::COMTypeInfo<nsSupportsWeakReference, void>::kIID = NS_ISUPPORTSWEAKREFERENCE_IID;
+
+namespace test {
+
+class SignalingAgent;
+
+std::string indent(const std::string &s, int width = 4) {
+ std::string prefix;
+ std::string out;
+ char previous = '\n';
+ prefix.assign(width, ' ');
+ for (std::string::const_iterator i = s.begin(); i != s.end(); i++) {
+ if (previous == '\n') {
+ out += prefix;
+ }
+ out += *i;
+ previous = *i;
+ }
+ return out;
+}
+
+static const std::string strSampleSdpAudioVideoNoIce =
+ "v=0\r\n"
+ "o=Mozilla-SIPUA 4949 0 IN IP4 10.86.255.143\r\n"
+ "s=SIP Call\r\n"
+ "t=0 0\r\n"
+ "a=ice-ufrag:qkEP\r\n"
+ "a=ice-pwd:ed6f9GuHjLcoCN6sC/Eh7fVl\r\n"
+ "m=audio 16384 RTP/AVP 0 8 9 101\r\n"
+ "c=IN IP4 10.86.255.143\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "a=rtpmap:8 PCMA/8000\r\n"
+ "a=rtpmap:9 G722/8000\r\n"
+ "a=rtpmap:101 telephone-event/8000\r\n"
+ "a=fmtp:101 0-15\r\n"
+ "a=sendrecv\r\n"
+ "a=candidate:1 1 UDP 2130706431 192.168.2.1 50005 typ host\r\n"
+ "a=candidate:2 2 UDP 2130706431 192.168.2.2 50006 typ host\r\n"
+ "m=video 1024 RTP/AVP 97\r\n"
+ "c=IN IP4 10.86.255.143\r\n"
+ "a=rtpmap:120 VP8/90000\r\n"
+ "a=fmtp:97 profile-level-id=42E00C\r\n"
+ "a=sendrecv\r\n"
+ "a=candidate:1 1 UDP 2130706431 192.168.2.3 50007 typ host\r\n"
+ "a=candidate:2 2 UDP 2130706431 192.168.2.4 50008 typ host\r\n";
+
+static const std::string strSampleCandidate =
+ "a=candidate:1 1 UDP 2130706431 192.168.2.1 50005 typ host\r\n";
+
+static const std::string strSampleMid = "sdparta";
+
+static const unsigned short nSamplelevel = 2;
+
+static const std::string strG711SdpOffer =
+ "v=0\r\n"
+ "o=- 1 1 IN IP4 148.147.200.251\r\n"
+ "s=-\r\n"
+ "b=AS:64\r\n"
+ "t=0 0\r\n"
+ "a=fingerprint:sha-256 F3:FA:20:C0:CD:48:C4:5F:02:5F:A5:D3:21:D0:2D:48:"
+ "7B:31:60:5C:5A:D8:0D:CD:78:78:6C:6D:CE:CC:0C:67\r\n"
+ "m=audio 9000 RTP/AVP 0 8 126\r\n"
+ "c=IN IP4 148.147.200.251\r\n"
+ "b=TIAS:64000\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "a=rtpmap:8 PCMA/8000\r\n"
+ "a=rtpmap:126 telephone-event/8000\r\n"
+ "a=candidate:0 1 udp 2130706432 148.147.200.251 9000 typ host\r\n"
+ "a=candidate:0 2 udp 2130706432 148.147.200.251 9005 typ host\r\n"
+ "a=ice-ufrag:cYuakxkEKH+RApYE\r\n"
+ "a=ice-pwd:bwtpzLZD+3jbu8vQHvEa6Xuq\r\n"
+ "a=setup:active\r\n"
+ "a=sendrecv\r\n";
+
+
+enum sdpTestFlags
+{
+ HAS_ALL_CANDIDATES = (1 << 0),
+};
+
+enum offerAnswerFlags
+{
+ OFFER_NONE = 0, // Sugar to make function calls clearer.
+ OFFER_AUDIO = (1<<0),
+ OFFER_VIDEO = (1<<1),
+ // Leaving some room here for other media types
+ ANSWER_NONE = 0, // Sugar to make function calls clearer.
+ ANSWER_AUDIO = (1<<8),
+ ANSWER_VIDEO = (1<<9),
+
+ OFFER_AV = OFFER_AUDIO | OFFER_VIDEO,
+ ANSWER_AV = ANSWER_AUDIO | ANSWER_VIDEO
+};
+
+ typedef enum {
+ NO_TRICKLE = 0,
+ OFFERER_TRICKLES = 1,
+ ANSWERER_TRICKLES = 2,
+ BOTH_TRICKLE = OFFERER_TRICKLES | ANSWERER_TRICKLES
+ } TrickleType;
+
+class TestObserver : public AFakePCObserver
+{
+protected:
+ ~TestObserver() {}
+
+public:
+ TestObserver(PeerConnectionImpl *peerConnection,
+ const std::string &aName) :
+ AFakePCObserver(peerConnection, aName),
+ lastAddIceStatusCode(PeerConnectionImpl::kNoError),
+ peerAgent(nullptr),
+ trickleCandidates(true)
+ {}
+
+ size_t MatchingCandidates(const std::string& cand) {
+ size_t count = 0;
+
+ for (size_t i=0; i<candidates.size(); ++i) {
+ if (candidates[i].find(cand) != std::string::npos)
+ ++count;
+ }
+
+ return count;
+ }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_IMETHOD OnCreateOfferSuccess(const char* offer, ER&) override;
+ NS_IMETHOD OnCreateOfferError(uint32_t code, const char *msg, ER&) override;
+ NS_IMETHOD OnCreateAnswerSuccess(const char* answer, ER&) override;
+ NS_IMETHOD OnCreateAnswerError(uint32_t code, const char *msg, ER&) override;
+ NS_IMETHOD OnSetLocalDescriptionSuccess(ER&) override;
+ NS_IMETHOD OnSetRemoteDescriptionSuccess(ER&) override;
+ NS_IMETHOD OnSetLocalDescriptionError(uint32_t code, const char *msg, ER&) override;
+ NS_IMETHOD OnSetRemoteDescriptionError(uint32_t code, const char *msg, ER&) override;
+ NS_IMETHOD NotifyDataChannel(nsIDOMDataChannel *channel, ER&) override;
+ NS_IMETHOD OnStateChange(PCObserverStateType state_type, ER&, void*) override;
+ NS_IMETHOD OnAddStream(DOMMediaStream &stream, ER&) override;
+ NS_IMETHOD OnRemoveStream(DOMMediaStream &stream, ER&) override;
+ NS_IMETHOD OnAddTrack(MediaStreamTrack &track, ER&) override;
+ NS_IMETHOD OnRemoveTrack(MediaStreamTrack &track, ER&) override;
+ NS_IMETHOD OnReplaceTrackSuccess(ER&) override;
+ NS_IMETHOD OnReplaceTrackError(uint32_t code, const char *msg, ER&) override;
+ NS_IMETHOD OnAddIceCandidateSuccess(ER&) override;
+ NS_IMETHOD OnAddIceCandidateError(uint32_t code, const char *msg, ER&) override;
+ NS_IMETHOD OnIceCandidate(uint16_t level, const char *mid, const char *cand, ER&) override;
+ NS_IMETHOD OnNegotiationNeeded(ER&) override;
+
+ // Hack because add_ice_candidates can happen asynchronously with respect
+ // to the API calls. The whole test suite needs a refactor.
+ ResponseState addIceCandidateState;
+ PeerConnectionImpl::Error lastAddIceStatusCode;
+
+ SignalingAgent* peerAgent;
+ bool trickleCandidates;
+};
+
+NS_IMPL_ISUPPORTS(TestObserver, nsISupportsWeakReference)
+
+NS_IMETHODIMP
+TestObserver::OnCreateOfferSuccess(const char* offer, ER&)
+{
+ lastString = offer;
+ state = stateSuccess;
+ std::cout << name << ": onCreateOfferSuccess = " << std::endl << indent(offer)
+ << std::endl;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TestObserver::OnCreateOfferError(uint32_t code, const char *message, ER&)
+{
+ lastStatusCode = static_cast<PeerConnectionImpl::Error>(code);
+ state = stateError;
+ std::cout << name << ": onCreateOfferError = " << code
+ << " (" << message << ")" << std::endl;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TestObserver::OnCreateAnswerSuccess(const char* answer, ER&)
+{
+ lastString = answer;
+ state = stateSuccess;
+ std::cout << name << ": onCreateAnswerSuccess =" << std::endl
+ << indent(answer) << std::endl;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TestObserver::OnCreateAnswerError(uint32_t code, const char *message, ER&)
+{
+ lastStatusCode = static_cast<PeerConnectionImpl::Error>(code);
+ std::cout << name << ": onCreateAnswerError = " << code
+ << " (" << message << ")" << std::endl;
+ state = stateError;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TestObserver::OnSetLocalDescriptionSuccess(ER&)
+{
+ lastStatusCode = PeerConnectionImpl::kNoError;
+ state = stateSuccess;
+ std::cout << name << ": onSetLocalDescriptionSuccess" << std::endl;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TestObserver::OnSetRemoteDescriptionSuccess(ER&)
+{
+ lastStatusCode = PeerConnectionImpl::kNoError;
+ state = stateSuccess;
+ std::cout << name << ": onSetRemoteDescriptionSuccess" << std::endl;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TestObserver::OnSetLocalDescriptionError(uint32_t code, const char *message, ER&)
+{
+ lastStatusCode = static_cast<PeerConnectionImpl::Error>(code);
+ state = stateError;
+ std::cout << name << ": onSetLocalDescriptionError = " << code
+ << " (" << message << ")" << std::endl;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TestObserver::OnSetRemoteDescriptionError(uint32_t code, const char *message, ER&)
+{
+ lastStatusCode = static_cast<PeerConnectionImpl::Error>(code);
+ state = stateError;
+ std::cout << name << ": onSetRemoteDescriptionError = " << code
+ << " (" << message << ")" << std::endl;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TestObserver::NotifyDataChannel(nsIDOMDataChannel *channel, ER&)
+{
+ std::cout << name << ": NotifyDataChannel" << std::endl;
+ return NS_OK;
+}
+
+static const char* PCImplSignalingStateStrings[] = {
+ "SignalingInvalid",
+ "SignalingStable",
+ "SignalingHaveLocalOffer",
+ "SignalingHaveRemoteOffer",
+ "SignalingHaveLocalPranswer",
+ "SignalingHaveRemotePranswer",
+ "SignalingClosed"
+};
+
+static const char* PCImplIceConnectionStateStrings[] = {
+ "new",
+ "checking",
+ "connected",
+ "completed",
+ "failed",
+ "disconnected",
+ "closed"
+};
+
+static const char* PCImplIceGatheringStateStrings[] = {
+ "new",
+ "gathering",
+ "complete"
+};
+
+#ifdef SIGNALING_UNITTEST_STANDALONE
+static_assert(ArrayLength(PCImplSignalingStateStrings) ==
+ size_t(PCImplSignalingState::EndGuard_),
+ "Table sizes must match");
+static_assert(ArrayLength(PCImplIceConnectionStateStrings) ==
+ size_t(PCImplIceConnectionState::EndGuard_),
+ "Table sizes must match");
+static_assert(ArrayLength(PCImplIceGatheringStateStrings) ==
+ size_t(PCImplIceGatheringState::EndGuard_),
+ "Table sizes must match");
+#endif // SIGNALING_UNITTEST_STANDALONE
+
+NS_IMETHODIMP
+TestObserver::OnStateChange(PCObserverStateType state_type, ER&, void*)
+{
+ nsresult rv;
+ PCImplIceConnectionState gotice;
+ PCImplIceGatheringState goticegathering;
+ PCImplSignalingState gotsignaling;
+
+ std::cout << name << ": ";
+
+ switch (state_type)
+ {
+ case PCObserverStateType::IceConnectionState:
+ MOZ_ASSERT(NS_IsMainThread());
+ rv = pc->IceConnectionState(&gotice);
+ NS_ENSURE_SUCCESS(rv, rv);
+ std::cout << "ICE Connection State: "
+ << PCImplIceConnectionStateStrings[int(gotice)]
+ << std::endl;
+ break;
+ case PCObserverStateType::IceGatheringState:
+ MOZ_ASSERT(NS_IsMainThread());
+ rv = pc->IceGatheringState(&goticegathering);
+ NS_ENSURE_SUCCESS(rv, rv);
+ std::cout
+ << "ICE Gathering State: "
+ << PCImplIceGatheringStateStrings[int(goticegathering)]
+ << std::endl;
+ break;
+ case PCObserverStateType::SdpState:
+ std::cout << "SDP State: " << std::endl;
+ // NS_ENSURE_SUCCESS(rv, rv);
+ break;
+ case PCObserverStateType::SignalingState:
+ MOZ_ASSERT(NS_IsMainThread());
+ rv = pc->SignalingState(&gotsignaling);
+ NS_ENSURE_SUCCESS(rv, rv);
+ std::cout << "Signaling State: "
+ << PCImplSignalingStateStrings[int(gotsignaling)]
+ << std::endl;
+ break;
+ default:
+ // Unknown State
+ MOZ_CRASH("Unknown state change type.");
+ break;
+ }
+
+ lastStateType = state_type;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+TestObserver::OnAddStream(DOMMediaStream &stream, ER&)
+{
+ std::cout << name << ": OnAddStream called hints=" << stream.GetHintContents()
+ << " thread=" << PR_GetCurrentThread() << std::endl ;
+
+ onAddStreamCalled = true;
+
+ streams.push_back(&stream);
+
+ // We know that the media stream is secretly a Fake_SourceMediaStream,
+ // so now we can start it pulling from us
+ RefPtr<Fake_SourceMediaStream> fs =
+ static_cast<Fake_SourceMediaStream *>(stream.GetStream());
+
+ test_utils->sts_target()->Dispatch(
+ WrapRunnable(fs, &Fake_SourceMediaStream::Start),
+ NS_DISPATCH_NORMAL);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TestObserver::OnRemoveStream(DOMMediaStream &stream, ER&)
+{
+ state = stateSuccess;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TestObserver::OnAddTrack(MediaStreamTrack &track, ER&)
+{
+ state = stateSuccess;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TestObserver::OnRemoveTrack(MediaStreamTrack &track, ER&)
+{
+ state = stateSuccess;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TestObserver::OnReplaceTrackSuccess(ER&)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TestObserver::OnReplaceTrackError(uint32_t code, const char *message, ER&)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TestObserver::OnAddIceCandidateSuccess(ER&)
+{
+ lastAddIceStatusCode = PeerConnectionImpl::kNoError;
+ addIceCandidateState = TestObserver::stateSuccess;
+ std::cout << name << ": onAddIceCandidateSuccess" << std::endl;
+ addIceSuccessCount++;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TestObserver::OnAddIceCandidateError(uint32_t code, const char *message, ER&)
+{
+ lastAddIceStatusCode = static_cast<PeerConnectionImpl::Error>(code);
+ addIceCandidateState = TestObserver::stateError;
+ std::cout << name << ": onAddIceCandidateError = " << code
+ << " (" << message << ")" << std::endl;
+ return NS_OK;
+}
+
+class ParsedSDP {
+ public:
+
+ explicit ParsedSDP(const std::string &sdp)
+ {
+ Parse(sdp);
+ }
+
+ void DeleteLines(const std::string &objType,
+ uint32_t limit = UINT32_MAX)
+ {
+ for (auto it = sdp_lines_.begin(); it != sdp_lines_.end() && limit;) {
+ auto temp = it;
+ ++it;
+ if (temp->first == objType) {
+ sdp_lines_.erase(temp);
+ --limit;
+ }
+ }
+ }
+
+ void DeleteLine(const std::string &objType)
+ {
+ DeleteLines(objType, 1);
+ }
+
+ // Replaces the index-th instance of objType in the SDP with
+ // a new string.
+ // If content is an empty string then the line will be removed
+ void ReplaceLine(const std::string &objType,
+ const std::string &content,
+ size_t index = 0)
+ {
+ auto it = FindLine(objType, index);
+ if(it != sdp_lines_.end()) {
+ if (content.empty()) {
+ sdp_lines_.erase(it);
+ } else {
+ (*it) = MakeKeyValue(content);
+ }
+ }
+ }
+
+ void AddLine(const std::string &content)
+ {
+ sdp_lines_.push_back(MakeKeyValue(content));
+ }
+
+ static std::pair<std::string, std::string> MakeKeyValue(
+ const std::string &content)
+ {
+ size_t whiteSpace = content.find(' ');
+ std::string key;
+ std::string value;
+ if (whiteSpace == std::string::npos) {
+ //this is the line with no extra contents
+ //example, v=0, a=sendrecv
+ key = content.substr(0, content.size() - 2);
+ value = "\r\n"; // Checking code assumes this is here.
+ } else {
+ key = content.substr(0, whiteSpace);
+ value = content.substr(whiteSpace+1);
+ }
+ return std::make_pair(key, value);
+ }
+
+ std::list<std::pair<std::string, std::string>>::iterator FindLine(
+ const std::string& objType,
+ size_t index = 0)
+ {
+ for (auto it = sdp_lines_.begin(); it != sdp_lines_.end(); ++it) {
+ if (it->first == objType) {
+ if (index == 0) {
+ return it;
+ }
+ --index;
+ }
+ }
+ return sdp_lines_.end();
+ }
+
+ void InsertLineAfter(const std::string &objType,
+ const std::string &content,
+ size_t index = 0)
+ {
+ auto it = FindLine(objType, index);
+ if (it != sdp_lines_.end()) {
+ sdp_lines_.insert(++it, MakeKeyValue(content));
+ }
+ }
+
+ // Returns the values for all lines of the indicated type
+ // Removes trailing "\r\n" from values.
+ std::vector<std::string> GetLines(std::string objType) const
+ {
+ std::vector<std::string> values;
+ for (auto it = sdp_lines_.begin(); it != sdp_lines_.end(); ++it) {
+ if (it->first == objType) {
+ std::string value = it->second;
+ if (value.find("\r") != std::string::npos) {
+ value = value.substr(0, value.find("\r"));
+ } else {
+ ADD_FAILURE() << "SDP line had no endline; this should never happen.";
+ }
+ values.push_back(value);
+ }
+ }
+ return values;
+ }
+
+ //Parse SDP as std::string into map that looks like:
+ // key: sdp content till first space
+ // value: sdp content after the first space, _including_ \r\n
+ void Parse(const std::string &sdp)
+ {
+ size_t prev = 0;
+ size_t found = 0;
+ for(;;) {
+ found = sdp.find('\n', found + 1);
+ if (found == std::string::npos)
+ break;
+ std::string line = sdp.substr(prev, (found - prev) + 1);
+ sdp_lines_.push_back(MakeKeyValue(line));
+
+ prev = found + 1;
+ }
+ }
+
+ //Convert Internal SDP representation into String representation
+ std::string getSdp() const
+ {
+ std::string sdp;
+
+ for (auto it = sdp_lines_.begin(); it != sdp_lines_.end(); ++it) {
+ sdp += it->first;
+ if (it->second != "\r\n") {
+ sdp += " ";
+ }
+ sdp += it->second;
+ }
+
+ return sdp;
+ }
+
+ void IncorporateCandidate(uint16_t level, const std::string &candidate)
+ {
+ std::string candidate_attribute("a=" + candidate + "\r\n");
+ // InsertLineAfter is 0 indexed, but level is 1 indexed
+ // This assumes that we have only media-level c lines.
+ InsertLineAfter("c=IN", candidate_attribute, level - 1);
+ }
+
+ std::list<std::pair<std::string, std::string>> sdp_lines_;
+};
+
+
+// This class wraps the PeerConnection object and ensures that all calls
+// into it happen on the main thread.
+class PCDispatchWrapper : public nsSupportsWeakReference
+{
+ protected:
+ virtual ~PCDispatchWrapper() {}
+
+ public:
+ explicit PCDispatchWrapper(const RefPtr<PeerConnectionImpl>& peerConnection)
+ : pc_(peerConnection) {}
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ PeerConnectionImpl *pcImpl() const {
+ return pc_;
+ }
+
+ const RefPtr<PeerConnectionMedia>& media() const {
+ return pc_->media();
+ }
+
+ NS_IMETHODIMP Initialize(TestObserver* aObserver,
+ nsGlobalWindow* aWindow,
+ const PeerConnectionConfiguration& aConfiguration,
+ nsIThread* aThread) {
+ nsresult rv;
+
+ observer_ = aObserver;
+
+ if (NS_IsMainThread()) {
+ rv = pc_->Initialize(*aObserver, aWindow, aConfiguration, aThread);
+ } else {
+ // It would have been preferable here to dispatch directly to
+ // PeerConnectionImpl::Initialize but since all the PC methods
+ // have overrides clang will throw a 'couldn't infer template
+ // argument' error.
+ // Instead we are dispatching back to the same method for
+ // all of these.
+ gMainThread->Dispatch(
+ WrapRunnableRet(&rv, this, &PCDispatchWrapper::Initialize,
+ aObserver, aWindow, aConfiguration, aThread),
+ NS_DISPATCH_SYNC);
+ rv = NS_OK;
+ }
+
+ return rv;
+ }
+
+ NS_IMETHODIMP CreateOffer(const mozilla::JsepOfferOptions& aOptions) {
+ nsresult rv;
+
+ if (NS_IsMainThread()) {
+ rv = pc_->CreateOffer(aOptions);
+ EXPECT_EQ(NS_OK, rv);
+ if (NS_FAILED(rv))
+ return rv;
+ EXPECT_EQ(TestObserver::stateSuccess, observer_->state);
+ if (observer_->state != TestObserver::stateSuccess) {
+ return NS_ERROR_FAILURE;
+ }
+ } else {
+ gMainThread->Dispatch(
+ WrapRunnableRet(&rv, this, &PCDispatchWrapper::CreateOffer, aOptions),
+ NS_DISPATCH_SYNC);
+ }
+
+ return rv;
+ }
+
+ NS_IMETHODIMP CreateAnswer() {
+ nsresult rv;
+
+ if (NS_IsMainThread()) {
+ rv = pc_->CreateAnswer();
+ } else {
+ gMainThread->Dispatch(
+ WrapRunnableRet(&rv, this, &PCDispatchWrapper::CreateAnswer),
+ NS_DISPATCH_SYNC);
+ }
+
+ return rv;
+ }
+
+ NS_IMETHODIMP SetLocalDescription (int32_t aAction, const char* aSDP) {
+ nsresult rv;
+
+ if (NS_IsMainThread()) {
+ rv = pc_->SetLocalDescription(aAction, aSDP);
+ } else {
+ gMainThread->Dispatch(
+ WrapRunnableRet(&rv, this, &PCDispatchWrapper::SetLocalDescription,
+ aAction, aSDP),
+ NS_DISPATCH_SYNC);
+ }
+
+ return rv;
+ }
+
+ NS_IMETHODIMP SetRemoteDescription (int32_t aAction, const char* aSDP) {
+ nsresult rv;
+
+ if (NS_IsMainThread()) {
+ rv = pc_->SetRemoteDescription(aAction, aSDP);
+ } else {
+ gMainThread->Dispatch(
+ WrapRunnableRet(&rv, this, &PCDispatchWrapper::SetRemoteDescription,
+ aAction, aSDP),
+ NS_DISPATCH_SYNC);
+ }
+
+ return rv;
+ }
+
+ NS_IMETHODIMP AddIceCandidate(const char* aCandidate, const char* aMid,
+ unsigned short aLevel) {
+ nsresult rv;
+
+ if (NS_IsMainThread()) {
+ rv = pc_->AddIceCandidate(aCandidate, aMid, aLevel);
+ } else {
+ gMainThread->Dispatch(
+ WrapRunnableRet(&rv, this, &PCDispatchWrapper::AddIceCandidate,
+ aCandidate, aMid, aLevel),
+ NS_DISPATCH_SYNC);
+ }
+ return rv;
+ }
+
+ NS_IMETHODIMP AddTrack(MediaStreamTrack *aTrack,
+ DOMMediaStream *aMediaStream)
+ {
+ nsresult rv;
+
+ if (NS_IsMainThread()) {
+ rv = pc_->AddTrack(*aTrack, *aMediaStream);
+ } else {
+ gMainThread->Dispatch(
+ WrapRunnableRet(&rv, this, &PCDispatchWrapper::AddTrack, aTrack,
+ aMediaStream),
+ NS_DISPATCH_SYNC);
+ }
+
+ return rv;
+ }
+
+ NS_IMETHODIMP RemoveTrack(MediaStreamTrack *aTrack) {
+ nsresult rv;
+
+ if (NS_IsMainThread()) {
+ rv = pc_->RemoveTrack(*aTrack);
+ } else {
+ gMainThread->Dispatch(
+ WrapRunnableRet(&rv, this, &PCDispatchWrapper::RemoveTrack, aTrack),
+ NS_DISPATCH_SYNC);
+ }
+
+ return rv;
+ }
+
+ NS_IMETHODIMP GetLocalDescription(char** aSDP) {
+ nsresult rv;
+
+ if (NS_IsMainThread()) {
+ rv = pc_->GetLocalDescription(aSDP);
+ } else {
+ gMainThread->Dispatch(
+ WrapRunnableRet(&rv, this, &PCDispatchWrapper::GetLocalDescription,
+ aSDP),
+ NS_DISPATCH_SYNC);
+ }
+
+ return rv;
+ }
+
+ NS_IMETHODIMP GetRemoteDescription(char** aSDP) {
+ nsresult rv;
+
+ if (NS_IsMainThread()) {
+ rv = pc_->GetRemoteDescription(aSDP);
+ } else {
+ gMainThread->Dispatch(
+ WrapRunnableRet(&rv, this, &PCDispatchWrapper::GetRemoteDescription,
+ aSDP),
+ NS_DISPATCH_SYNC);
+ }
+
+ return rv;
+ }
+
+ mozilla::dom::PCImplSignalingState SignalingState() {
+ mozilla::dom::PCImplSignalingState result;
+
+ if (NS_IsMainThread()) {
+ result = pc_->SignalingState();
+ } else {
+ gMainThread->Dispatch(
+ WrapRunnableRet(&result, this, &PCDispatchWrapper::SignalingState),
+ NS_DISPATCH_SYNC);
+ }
+
+ return result;
+ }
+
+ mozilla::dom::PCImplIceConnectionState IceConnectionState() {
+ mozilla::dom::PCImplIceConnectionState result;
+
+ if (NS_IsMainThread()) {
+ result = pc_->IceConnectionState();
+ } else {
+ gMainThread->Dispatch(
+ WrapRunnableRet(&result, this, &PCDispatchWrapper::IceConnectionState),
+ NS_DISPATCH_SYNC);
+ }
+
+ return result;
+ }
+
+ mozilla::dom::PCImplIceGatheringState IceGatheringState() {
+ mozilla::dom::PCImplIceGatheringState result;
+
+ if (NS_IsMainThread()) {
+ result = pc_->IceGatheringState();
+ } else {
+ gMainThread->Dispatch(
+ WrapRunnableRet(&result, this, &PCDispatchWrapper::IceGatheringState),
+ NS_DISPATCH_SYNC);
+ }
+
+ return result;
+ }
+
+ NS_IMETHODIMP Close() {
+ nsresult rv;
+
+ if (NS_IsMainThread()) {
+ rv = pc_->Close();
+ } else {
+ gMainThread->Dispatch(
+ WrapRunnableRet(&rv, this, &PCDispatchWrapper::Close),
+ NS_DISPATCH_SYNC);
+ }
+
+ return rv;
+ }
+
+ private:
+ RefPtr<PeerConnectionImpl> pc_;
+ RefPtr<TestObserver> observer_;
+};
+
+NS_IMPL_ISUPPORTS(PCDispatchWrapper, nsISupportsWeakReference)
+
+
+struct Msid
+{
+ std::string streamId;
+ std::string trackId;
+ bool operator<(const Msid& other) const {
+ if (streamId < other.streamId) {
+ return true;
+ }
+
+ if (streamId > other.streamId) {
+ return false;
+ }
+
+ return trackId < other.trackId;
+ }
+};
+
+class SignalingAgent {
+ public:
+ explicit SignalingAgent(const std::string &aName,
+ const std::string stun_addr = g_stun_server_address,
+ uint16_t stun_port = g_stun_server_port) :
+ pc(nullptr),
+ name(aName),
+ mBundleEnabled(true),
+ mExpectedFrameRequestType(VideoSessionConduit::FrameRequestPli),
+ mExpectNack(true),
+ mExpectRtcpMuxAudio(true),
+ mExpectRtcpMuxVideo(true),
+ mRemoteDescriptionSet(false) {
+ cfg_.addStunServer(stun_addr, stun_port, kNrIceTransportUdp);
+ cfg_.addStunServer(stun_addr, stun_port, kNrIceTransportTcp);
+
+ PeerConnectionImpl *pcImpl =
+ PeerConnectionImpl::CreatePeerConnection();
+ EXPECT_TRUE(pcImpl);
+ pcImpl->SetAllowIceLoopback(true);
+ pcImpl->SetAllowIceLinkLocal(true);
+ pc = new PCDispatchWrapper(pcImpl);
+ }
+
+
+ ~SignalingAgent() {
+ mozilla::SyncRunnable::DispatchToThread(gMainThread,
+ WrapRunnable(this, &SignalingAgent::Close));
+ }
+
+ void Init_m()
+ {
+ pObserver = new TestObserver(pc->pcImpl(), name);
+ ASSERT_TRUE(pObserver);
+
+ ASSERT_EQ(pc->Initialize(pObserver, nullptr, cfg_, gMainThread), NS_OK);
+ }
+
+ void Init()
+ {
+ mozilla::SyncRunnable::DispatchToThread(gMainThread,
+ WrapRunnable(this, &SignalingAgent::Init_m));
+ }
+
+ void SetBundleEnabled(bool enabled)
+ {
+ mBundleEnabled = enabled;
+ }
+
+ void SetBundlePolicy(JsepBundlePolicy policy)
+ {
+ cfg_.setBundlePolicy(policy);
+ }
+
+ void SetExpectedFrameRequestType(VideoSessionConduit::FrameRequestType type)
+ {
+ mExpectedFrameRequestType = type;
+ }
+
+ void WaitForGather() {
+ ASSERT_TRUE_WAIT(ice_gathering_state() == PCImplIceGatheringState::Complete,
+ kDefaultTimeout);
+
+ std::cout << name << ": Init Complete" << std::endl;
+
+ // Check that the default candidate has been filled out with something
+ std::string localSdp = getLocalDescription();
+
+ std::cout << "Local SDP after gather: " << localSdp;
+ ASSERT_EQ(std::string::npos, localSdp.find("c=IN IP4 0.0.0.0"));
+ ASSERT_EQ(std::string::npos, localSdp.find("m=video 9 "));
+ ASSERT_EQ(std::string::npos, localSdp.find("m=audio 9 "));
+
+ // TODO(bug 1098584): Check for end-of-candidates attr
+ }
+
+ bool WaitForGatherAllowFail() {
+ EXPECT_TRUE_WAIT(
+ ice_gathering_state() == PCImplIceGatheringState::Complete ||
+ ice_connection_state() == PCImplIceConnectionState::Failed,
+ kDefaultTimeout);
+
+ if (ice_connection_state() == PCImplIceConnectionState::Failed) {
+ std::cout << name << ": Init Failed" << std::endl;
+ return false;
+ }
+
+ std::cout << name << "Init Complete" << std::endl;
+ return true;
+ }
+
+ void DropOutgoingTrickleCandidates() {
+ pObserver->trickleCandidates = false;
+ }
+
+ PCImplIceConnectionState ice_connection_state()
+ {
+ return pc->IceConnectionState();
+ }
+
+ PCImplIceGatheringState ice_gathering_state()
+ {
+ return pc->IceGatheringState();
+ }
+
+ PCImplSignalingState signaling_state()
+ {
+ return pc->SignalingState();
+ }
+
+ void Close()
+ {
+ std::cout << name << ": Close" << std::endl;
+
+ pc->Close();
+ pc = nullptr;
+ pObserver = nullptr;
+ }
+
+ bool OfferContains(const std::string& str) {
+ return offer().find(str) != std::string::npos;
+ }
+
+ bool AnswerContains(const std::string& str) {
+ return answer().find(str) != std::string::npos;
+ }
+
+ size_t MatchingCandidates(const std::string& cand) {
+ return pObserver->MatchingCandidates(cand);
+ }
+
+ const std::string& offer() const { return offer_; }
+ const std::string& answer() const { return answer_; }
+
+ std::string getLocalDescription() const {
+ char *sdp = nullptr;
+ pc->GetLocalDescription(&sdp);
+ if (!sdp) {
+ return "";
+ }
+ std::string result(sdp);
+ delete sdp;
+ return result;
+ }
+
+ std::string getRemoteDescription() const {
+ char *sdp = 0;
+ pc->GetRemoteDescription(&sdp);
+ if (!sdp) {
+ return "";
+ }
+ std::string result(sdp);
+ delete sdp;
+ return result;
+ }
+
+ std::string RemoveBundle(const std::string& sdp) const {
+ ParsedSDP parsed(sdp);
+ parsed.DeleteLines("a=group:BUNDLE");
+ return parsed.getSdp();
+ }
+
+ // Adds a stream to the PeerConnection.
+ void AddStream(uint32_t hint =
+ DOMMediaStream::HINT_CONTENTS_AUDIO |
+ DOMMediaStream::HINT_CONTENTS_VIDEO,
+ MediaStream *stream = nullptr) {
+
+ if (!stream && (hint & DOMMediaStream::HINT_CONTENTS_AUDIO)) {
+ // Useful default
+ // Create a media stream as if it came from GUM
+ Fake_AudioStreamSource *audio_stream =
+ new Fake_AudioStreamSource();
+
+ nsresult ret;
+ mozilla::SyncRunnable::DispatchToThread(
+ test_utils->sts_target(),
+ WrapRunnableRet(&ret, audio_stream, &Fake_MediaStream::Start));
+
+ ASSERT_TRUE(NS_SUCCEEDED(ret));
+ stream = audio_stream;
+ }
+
+ RefPtr<DOMMediaStream> domMediaStream = new DOMMediaStream(stream);
+ domMediaStream->SetHintContents(hint);
+
+ nsTArray<RefPtr<MediaStreamTrack>> tracks;
+ domMediaStream->GetTracks(tracks);
+ for (uint32_t i = 0; i < tracks.Length(); i++) {
+ Msid msid = {domMediaStream->GetId(), tracks[i]->GetId()};
+
+ ASSERT_FALSE(mAddedTracks.count(msid))
+ << msid.streamId << "/" << msid.trackId << " already added";
+
+ mAddedTracks[msid] = (tracks[i]->AsVideoStreamTrack() ?
+ SdpMediaSection::kVideo :
+ SdpMediaSection::kAudio);
+
+ ASSERT_EQ(pc->AddTrack(tracks[i], domMediaStream), NS_OK);
+ }
+ domMediaStreams_.push_back(domMediaStream);
+ }
+
+ // I would love to make this an overload of operator<<, but there's no way to
+ // declare it in a way that works with gtest's header files.
+ std::string DumpTracks(
+ const std::map<Msid, SdpMediaSection::MediaType>& tracks) const
+ {
+ std::ostringstream oss;
+ for (auto it = tracks.begin(); it != tracks.end(); ++it) {
+ oss << it->first.streamId << "/" << it->first.trackId
+ << " (" << it->second << ")" << std::endl;
+ }
+
+ return oss.str();
+ }
+
+ void ExpectMissingTracks(SdpMediaSection::MediaType type)
+ {
+ for (auto it = mAddedTracks.begin(); it != mAddedTracks.end();) {
+ if (it->second == type) {
+ auto temp = it;
+ ++it;
+ mAddedTracks.erase(temp);
+ } else {
+ ++it;
+ }
+ }
+ }
+
+ void CheckLocalPipeline(const std::string& streamId,
+ const std::string& trackId,
+ SdpMediaSection::MediaType type,
+ int pipelineCheckFlags = 0) const
+ {
+ LocalSourceStreamInfo* info;
+ mozilla::SyncRunnable::DispatchToThread(
+ gMainThread, WrapRunnableRet(&info,
+ pc->media(), &PeerConnectionMedia::GetLocalStreamById,
+ streamId));
+
+ ASSERT_TRUE(info) << "No such local stream id: " << streamId;
+
+ RefPtr<MediaPipeline> pipeline;
+
+ mozilla::SyncRunnable::DispatchToThread(
+ gMainThread,
+ WrapRunnableRet(&pipeline, info,
+ &SourceStreamInfo::GetPipelineByTrackId_m,
+ trackId));
+
+ ASSERT_TRUE(pipeline) << "No such local track id: " << trackId;
+
+ if (type == SdpMediaSection::kVideo) {
+ ASSERT_TRUE(pipeline->IsVideo()) << "Local track " << trackId
+ << " was not video";
+ ASSERT_EQ(mExpectRtcpMuxVideo, pipeline->IsDoingRtcpMux())
+ << "Pipeline for remote track " << trackId
+ << " is" << (mExpectRtcpMuxVideo ? " not " : " ") << "using rtcp-mux";
+ // No checking for video RTP yet, since we don't have support for fake
+ // video here yet. (bug 1142320)
+ } else {
+ ASSERT_FALSE(pipeline->IsVideo()) << "Local track " << trackId
+ << " was not audio";
+ WAIT(pipeline->rtp_packets_sent() >= 4 &&
+ pipeline->rtcp_packets_received() >= 1,
+ kDefaultTimeout);
+ ASSERT_LE(4, pipeline->rtp_packets_sent())
+ << "Local track " << trackId << " isn't sending RTP";
+ ASSERT_LE(1, pipeline->rtcp_packets_received())
+ << "Local track " << trackId << " isn't receiving RTCP";
+ ASSERT_EQ(mExpectRtcpMuxAudio, pipeline->IsDoingRtcpMux())
+ << "Pipeline for remote track " << trackId
+ << " is" << (mExpectRtcpMuxAudio ? " not " : " ") << "using rtcp-mux";
+ }
+ }
+
+ void CheckRemotePipeline(const std::string& streamId,
+ const std::string& trackId,
+ SdpMediaSection::MediaType type,
+ int pipelineCheckFlags = 0) const
+ {
+ RemoteSourceStreamInfo* info;
+ mozilla::SyncRunnable::DispatchToThread(
+ gMainThread, WrapRunnableRet(&info,
+ pc->media(), &PeerConnectionMedia::GetRemoteStreamById,
+ streamId));
+
+ ASSERT_TRUE(info) << "No such remote stream id: " << streamId;
+
+ RefPtr<MediaPipeline> pipeline;
+
+ mozilla::SyncRunnable::DispatchToThread(
+ gMainThread,
+ WrapRunnableRet(&pipeline, info,
+ &SourceStreamInfo::GetPipelineByTrackId_m,
+ trackId));
+
+ ASSERT_TRUE(pipeline) << "No such remote track id: " << trackId;
+
+ if (type == SdpMediaSection::kVideo) {
+ ASSERT_TRUE(pipeline->IsVideo()) << "Remote track " << trackId
+ << " was not video";
+ mozilla::MediaSessionConduit *conduit = pipeline->Conduit();
+ ASSERT_TRUE(conduit);
+ ASSERT_EQ(conduit->type(), mozilla::MediaSessionConduit::VIDEO);
+ mozilla::VideoSessionConduit *video_conduit =
+ static_cast<mozilla::VideoSessionConduit*>(conduit);
+ ASSERT_EQ(mExpectNack, video_conduit->UsingNackBasic());
+ ASSERT_EQ(mExpectedFrameRequestType,
+ video_conduit->FrameRequestMethod());
+ ASSERT_EQ(mExpectRtcpMuxVideo, pipeline->IsDoingRtcpMux())
+ << "Pipeline for remote track " << trackId
+ << " is" << (mExpectRtcpMuxVideo ? " not " : " ") << "using rtcp-mux";
+ // No checking for video RTP yet, since we don't have support for fake
+ // video here yet. (bug 1142320)
+ } else {
+ ASSERT_FALSE(pipeline->IsVideo()) << "Remote track " << trackId
+ << " was not audio";
+ WAIT(pipeline->rtp_packets_received() >= 4 &&
+ pipeline->rtcp_packets_sent() >= 1,
+ kDefaultTimeout);
+ ASSERT_LE(4, pipeline->rtp_packets_received())
+ << "Remote track " << trackId << " isn't receiving RTP";
+ ASSERT_LE(1, pipeline->rtcp_packets_sent())
+ << "Remote track " << trackId << " isn't sending RTCP";
+ ASSERT_EQ(mExpectRtcpMuxAudio, pipeline->IsDoingRtcpMux())
+ << "Pipeline for remote track " << trackId
+ << " is" << (mExpectRtcpMuxAudio ? " not " : " ") << "using rtcp-mux";
+ }
+ }
+
+ void RemoveTrack(size_t streamIndex, bool videoTrack = false)
+ {
+ ASSERT_LT(streamIndex, domMediaStreams_.size());
+ nsTArray<RefPtr<MediaStreamTrack>> tracks;
+ domMediaStreams_[streamIndex]->GetTracks(tracks);
+ for (size_t i = 0; i < tracks.Length(); ++i) {
+ if (!!tracks[i]->AsVideoStreamTrack() == videoTrack) {
+ Msid msid;
+ msid.streamId = domMediaStreams_[streamIndex]->GetId();
+ msid.trackId = tracks[i]->GetId();
+ mAddedTracks.erase(msid);
+ ASSERT_EQ(pc->RemoveTrack(tracks[i]), NS_OK);
+ }
+ }
+ }
+
+ void RemoveStream(size_t index) {
+ nsTArray<RefPtr<MediaStreamTrack>> tracks;
+ domMediaStreams_[index]->GetTracks(tracks);
+ for (uint32_t i = 0; i < tracks.Length(); i++) {
+ ASSERT_EQ(pc->RemoveTrack(tracks[i]), NS_OK);
+ }
+ domMediaStreams_.erase(domMediaStreams_.begin() + index);
+ }
+
+ // Removes the stream that was most recently added to the PeerConnection.
+ void RemoveLastStreamAdded() {
+ ASSERT_FALSE(domMediaStreams_.empty());
+ RemoveStream(domMediaStreams_.size() - 1);
+ }
+
+ void CreateOffer(OfferOptions& options,
+ uint32_t offerFlags,
+ PCImplSignalingState endState =
+ PCImplSignalingState::SignalingStable) {
+
+ uint32_t aHintContents = 0;
+ if (offerFlags & OFFER_AUDIO) {
+ aHintContents |= DOMMediaStream::HINT_CONTENTS_AUDIO;
+ }
+ if (offerFlags & OFFER_VIDEO) {
+ aHintContents |= DOMMediaStream::HINT_CONTENTS_VIDEO;
+ }
+ AddStream(aHintContents);
+
+ // Now call CreateOffer as JS would
+ pObserver->state = TestObserver::stateNoResponse;
+ ASSERT_EQ(pc->CreateOffer(options), NS_OK);
+
+ ASSERT_EQ(pObserver->state, TestObserver::stateSuccess);
+ ASSERT_EQ(signaling_state(), endState);
+ offer_ = pObserver->lastString;
+ if (!mBundleEnabled) {
+ offer_ = RemoveBundle(offer_);
+ }
+ }
+
+ // sets the offer to match the local description
+ // which isn't good if you are the answerer
+ void UpdateOffer() {
+ offer_ = getLocalDescription();
+ if (!mBundleEnabled) {
+ offer_ = RemoveBundle(offer_);
+ }
+ }
+
+ void CreateAnswer(uint32_t offerAnswerFlags,
+ PCImplSignalingState endState =
+ PCImplSignalingState::SignalingHaveRemoteOffer) {
+ // Create a media stream as if it came from GUM
+ Fake_AudioStreamSource *audio_stream =
+ new Fake_AudioStreamSource();
+
+ nsresult ret;
+ mozilla::SyncRunnable::DispatchToThread(
+ test_utils->sts_target(),
+ WrapRunnableRet(&ret, audio_stream, &Fake_MediaStream::Start));
+
+ ASSERT_TRUE(NS_SUCCEEDED(ret));
+
+ uint32_t aHintContents = 0;
+ if (offerAnswerFlags & ANSWER_AUDIO) {
+ aHintContents |= DOMMediaStream::HINT_CONTENTS_AUDIO;
+ }
+ if (offerAnswerFlags & ANSWER_VIDEO) {
+ aHintContents |= DOMMediaStream::HINT_CONTENTS_VIDEO;
+ }
+ AddStream(aHintContents, audio_stream);
+
+ // Decide if streams are disabled for offer or answer
+ // then perform SDP checking based on which stream disabled
+ pObserver->state = TestObserver::stateNoResponse;
+ ASSERT_EQ(pc->CreateAnswer(), NS_OK);
+ ASSERT_EQ(pObserver->state, TestObserver::stateSuccess);
+ ASSERT_EQ(signaling_state(), endState);
+
+ answer_ = pObserver->lastString;
+ if (!mBundleEnabled) {
+ answer_ = RemoveBundle(answer_);
+ }
+ }
+
+ // sets the answer to match the local description
+ // which isn't good if you are the offerer
+ void UpdateAnswer() {
+ answer_ = getLocalDescription();
+ if (!mBundleEnabled) {
+ answer_ = RemoveBundle(answer_);
+ }
+ }
+
+ void CreateOfferRemoveTrack(OfferOptions& options, bool videoTrack) {
+
+ RemoveTrack(0, videoTrack);
+
+ // Now call CreateOffer as JS would
+ pObserver->state = TestObserver::stateNoResponse;
+ ASSERT_EQ(pc->CreateOffer(options), NS_OK);
+ ASSERT_TRUE(pObserver->state == TestObserver::stateSuccess);
+ offer_ = pObserver->lastString;
+ if (!mBundleEnabled) {
+ offer_ = RemoveBundle(offer_);
+ }
+ }
+
+ void SetRemote(TestObserver::Action action, const std::string& remote,
+ bool ignoreError = false,
+ PCImplSignalingState endState =
+ PCImplSignalingState::SignalingInvalid) {
+
+ if (endState == PCImplSignalingState::SignalingInvalid) {
+ endState = (action == TestObserver::OFFER ?
+ PCImplSignalingState::SignalingHaveRemoteOffer :
+ PCImplSignalingState::SignalingStable);
+ }
+
+ pObserver->state = TestObserver::stateNoResponse;
+ ASSERT_EQ(pc->SetRemoteDescription(action, remote.c_str()), NS_OK);
+ ASSERT_EQ(signaling_state(), endState);
+ if (!ignoreError) {
+ ASSERT_EQ(pObserver->state, TestObserver::stateSuccess);
+ }
+
+ mRemoteDescriptionSet = true;
+ for (auto i = deferredCandidates_.begin();
+ i != deferredCandidates_.end();
+ ++i) {
+ AddIceCandidate(i->candidate.c_str(),
+ i->mid.c_str(),
+ i->level,
+ i->expectSuccess);
+ }
+ deferredCandidates_.clear();
+ }
+
+ void SetLocal(TestObserver::Action action, const std::string& local,
+ bool ignoreError = false,
+ PCImplSignalingState endState =
+ PCImplSignalingState::SignalingInvalid) {
+
+ if (endState == PCImplSignalingState::SignalingInvalid) {
+ endState = (action == TestObserver::OFFER ?
+ PCImplSignalingState::SignalingHaveLocalOffer :
+ PCImplSignalingState::SignalingStable);
+ }
+
+ pObserver->state = TestObserver::stateNoResponse;
+ ASSERT_EQ(pc->SetLocalDescription(action, local.c_str()), NS_OK);
+ ASSERT_EQ(signaling_state(), endState);
+ if (!ignoreError) {
+ ASSERT_EQ(pObserver->state, TestObserver::stateSuccess);
+ }
+ }
+
+ typedef enum {
+ NORMAL_ENCODING,
+ CHROME_ENCODING
+ } TrickleEncoding;
+
+ bool IceCompleted() {
+ return pc->IceConnectionState() == PCImplIceConnectionState::Connected;
+ }
+
+ void AddIceCandidateStr(const std::string& candidate, const std::string& mid,
+ unsigned short level) {
+ if (!mRemoteDescriptionSet) {
+ // Not time to add this, because the unit-test code hasn't set the
+ // description yet.
+ DeferredCandidate candidateStruct = {candidate, mid, level, true};
+ deferredCandidates_.push_back(candidateStruct);
+ } else {
+ AddIceCandidate(candidate, mid, level, true);
+ }
+ }
+
+ void AddIceCandidate(const std::string& candidate, const std::string& mid, unsigned short level,
+ bool expectSuccess) {
+ PCImplSignalingState endState = signaling_state();
+ pObserver->addIceCandidateState = TestObserver::stateNoResponse;
+ pc->AddIceCandidate(candidate.c_str(), mid.c_str(), level);
+ ASSERT_TRUE(pObserver->addIceCandidateState ==
+ expectSuccess ? TestObserver::stateSuccess :
+ TestObserver::stateError
+ );
+
+ // Verify that adding ICE candidates does not change the signaling state
+ ASSERT_EQ(signaling_state(), endState);
+ ASSERT_NE("", mid);
+ }
+
+ int GetPacketsReceived(const std::string& streamId) const
+ {
+ std::vector<DOMMediaStream *> streams = pObserver->GetStreams();
+
+ for (size_t i = 0; i < streams.size(); ++i) {
+ if (streams[i]->GetId() == streamId) {
+ return GetPacketsReceived(i);
+ }
+ }
+
+ EXPECT_TRUE(false);
+ return 0;
+ }
+
+ int GetPacketsReceived(size_t stream) const {
+ std::vector<DOMMediaStream *> streams = pObserver->GetStreams();
+
+ if (streams.size() <= stream) {
+ EXPECT_TRUE(false);
+ return 0;
+ }
+
+ return streams[stream]->GetStream()->AsSourceStream()->GetSegmentsAdded();
+ }
+
+ int GetPacketsSent(const std::string& streamId) const
+ {
+ for (size_t i = 0; i < domMediaStreams_.size(); ++i) {
+ if (domMediaStreams_[i]->GetId() == streamId) {
+ return GetPacketsSent(i);
+ }
+ }
+
+ EXPECT_TRUE(false);
+ return 0;
+ }
+
+ int GetPacketsSent(size_t stream) const {
+ if (stream >= domMediaStreams_.size()) {
+ EXPECT_TRUE(false);
+ return 0;
+ }
+
+ return static_cast<Fake_MediaStreamBase *>(
+ domMediaStreams_[stream]->GetStream())->GetSegmentsAdded();
+ }
+
+ //Stops generating new audio data for transmission.
+ //Should be called before Cleanup of the peer connection.
+ void CloseSendStreams() {
+ for (auto i = domMediaStreams_.begin(); i != domMediaStreams_.end(); ++i) {
+ static_cast<Fake_MediaStream*>((*i)->GetStream())->StopStream();
+ }
+ }
+
+ //Stops pulling audio data off the receivers.
+ //Should be called before Cleanup of the peer connection.
+ void CloseReceiveStreams() {
+ std::vector<DOMMediaStream *> streams =
+ pObserver->GetStreams();
+ for (size_t i = 0; i < streams.size(); i++) {
+ streams[i]->GetStream()->AsSourceStream()->StopStream();
+ }
+ }
+
+ // Right now we have no convenient way for this unit-test to learn the track
+ // ids of the tracks, so they can be queried later. We could either expose
+ // the JsepSessionImpl in some way, or we could parse the identifiers out of
+ // the SDP. For now, we just specify audio/video, since a given DOMMediaStream
+ // can have only one of each anyway. Once this is fixed, we will need to
+ // pass a real track id if we want to test that case.
+ RefPtr<mozilla::MediaPipeline> GetMediaPipeline(
+ bool local, size_t stream, bool video) {
+ SourceStreamInfo* streamInfo;
+ if (local) {
+ mozilla::SyncRunnable::DispatchToThread(
+ gMainThread, WrapRunnableRet(&streamInfo,
+ pc->media(), &PeerConnectionMedia::GetLocalStreamByIndex,
+ stream));
+ } else {
+ mozilla::SyncRunnable::DispatchToThread(
+ gMainThread, WrapRunnableRet(&streamInfo,
+ pc->media(), &PeerConnectionMedia::GetRemoteStreamByIndex,
+ stream));
+ }
+
+ if (!streamInfo) {
+ return nullptr;
+ }
+
+ const auto &pipelines = streamInfo->GetPipelines();
+
+ for (auto i = pipelines.begin(); i != pipelines.end(); ++i) {
+ if (i->second->IsVideo() == video) {
+ std::cout << "Got MediaPipeline " << i->second->trackid();
+ return i->second;
+ }
+ }
+ return nullptr;
+ }
+
+ void SetPeer(SignalingAgent* peer) {
+ pObserver->peerAgent = peer;
+ }
+
+public:
+ RefPtr<PCDispatchWrapper> pc;
+ RefPtr<TestObserver> pObserver;
+ std::string offer_;
+ std::string answer_;
+ std::vector<RefPtr<DOMMediaStream>> domMediaStreams_;
+ PeerConnectionConfiguration cfg_;
+ const std::string name;
+ bool mBundleEnabled;
+ VideoSessionConduit::FrameRequestType mExpectedFrameRequestType;
+ bool mExpectNack;
+ bool mExpectRtcpMuxAudio;
+ bool mExpectRtcpMuxVideo;
+ bool mRemoteDescriptionSet;
+
+ std::map<Msid, SdpMediaSection::MediaType> mAddedTracks;
+
+ typedef struct {
+ std::string candidate;
+ std::string mid;
+ uint16_t level;
+ bool expectSuccess;
+ } DeferredCandidate;
+
+ std::list<DeferredCandidate> deferredCandidates_;
+};
+
+static void AddIceCandidateToPeer(nsWeakPtr weak_observer,
+ uint16_t level,
+ const std::string &mid,
+ const std::string &cand) {
+ nsCOMPtr<nsISupportsWeakReference> tmp = do_QueryReferent(weak_observer);
+ if (!tmp) {
+ return;
+ }
+
+ RefPtr<nsSupportsWeakReference> tmp2 = do_QueryObject(tmp);
+ RefPtr<TestObserver> observer = static_cast<TestObserver*>(&*tmp2);
+
+ if (!observer) {
+ return;
+ }
+
+ observer->candidates.push_back(cand);
+
+ if (!observer->peerAgent || !observer->trickleCandidates) {
+ return;
+ }
+
+ observer->peerAgent->AddIceCandidateStr(cand, mid, level);
+}
+
+
+NS_IMETHODIMP
+TestObserver::OnIceCandidate(uint16_t level,
+ const char * mid,
+ const char * candidate, ER&)
+{
+ if (strlen(candidate) != 0) {
+ std::cerr << name << ": got candidate: " << candidate << std::endl;
+ // Forward back to myself to unwind stack.
+ nsWeakPtr weak_this = do_GetWeakReference(this);
+ gMainThread->Dispatch(
+ WrapRunnableNM(
+ &AddIceCandidateToPeer,
+ weak_this,
+ level,
+ std::string(mid),
+ std::string(candidate)),
+ NS_DISPATCH_NORMAL);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TestObserver::OnNegotiationNeeded(ER&)
+{
+ return NS_OK;
+}
+
+class SignalingEnvironment : public ::testing::Environment {
+ public:
+ void TearDown() {
+ // Signaling is shut down in XPCOM shutdown
+ }
+};
+
+class SignalingAgentTest : public ::testing::Test {
+ public:
+ static void SetUpTestCase() {
+ }
+
+ void TearDown() {
+ // Delete all the agents.
+ for (size_t i=0; i < agents_.size(); i++) {
+ delete agents_[i];
+ }
+ }
+
+ bool CreateAgent() {
+ return CreateAgent(g_stun_server_address, g_stun_server_port);
+ }
+
+ bool CreateAgent(const std::string stun_addr, uint16_t stun_port) {
+ UniquePtr<SignalingAgent> agent(
+ new SignalingAgent("agent", stun_addr, stun_port));
+
+ agent->Init();
+
+ agents_.push_back(agent.release());
+
+ return true;
+ }
+
+ void CreateAgentNoInit() {
+ UniquePtr<SignalingAgent> agent(new SignalingAgent("agent"));
+ agents_.push_back(agent.release());
+ }
+
+ SignalingAgent *agent(size_t i) {
+ return agents_[i];
+ }
+
+ private:
+ std::vector<SignalingAgent *> agents_;
+};
+
+
+class SignalingTest : public ::testing::Test,
+ public ::testing::WithParamInterface<std::string>
+{
+public:
+ SignalingTest()
+ : init_(false),
+ a1_(nullptr),
+ a2_(nullptr),
+ stun_addr_(g_stun_server_address),
+ stun_port_(g_stun_server_port) {}
+
+ SignalingTest(const std::string& stun_addr, uint16_t stun_port)
+ : a1_(nullptr),
+ a2_(nullptr),
+ stun_addr_(stun_addr),
+ stun_port_(stun_port) {}
+
+ ~SignalingTest() {
+ if (init_) {
+ mozilla::SyncRunnable::DispatchToThread(gMainThread,
+ WrapRunnable(this, &SignalingTest::Teardown_m));
+ }
+ }
+
+ void Teardown_m() {
+ a1_->SetPeer(nullptr);
+ a2_->SetPeer(nullptr);
+ }
+
+ static void SetUpTestCase() {
+ }
+
+ void EnsureInit() {
+
+ if (init_)
+ return;
+
+ a1_ = MakeUnique<SignalingAgent>(callerName, stun_addr_, stun_port_);
+ a2_ = MakeUnique<SignalingAgent>(calleeName, stun_addr_, stun_port_);
+
+ if (GetParam() == "no_bundle") {
+ a1_->SetBundleEnabled(false);
+ } else if(GetParam() == "reject_bundle") {
+ a2_->SetBundleEnabled(false);
+ } else if (GetParam() == "max-bundle") {
+ a1_->SetBundlePolicy(JsepBundlePolicy::kBundleMaxBundle);
+ a2_->SetBundlePolicy(JsepBundlePolicy::kBundleMaxBundle);
+ } else if (GetParam() == "balanced") {
+ a1_->SetBundlePolicy(JsepBundlePolicy::kBundleBalanced);
+ a2_->SetBundlePolicy(JsepBundlePolicy::kBundleBalanced);
+ } else if (GetParam() == "max-compat") {
+ a1_->SetBundlePolicy(JsepBundlePolicy::kBundleMaxCompat);
+ a2_->SetBundlePolicy(JsepBundlePolicy::kBundleMaxCompat);
+ }
+
+ a1_->Init();
+ a2_->Init();
+ a1_->SetPeer(a2_.get());
+ a2_->SetPeer(a1_.get());
+
+ init_ = true;
+ }
+
+ bool UseBundle()
+ {
+ return (GetParam() != "no_bundle") && (GetParam() != "reject_bundle");
+ }
+
+ void WaitForGather() {
+ a1_->WaitForGather();
+ a2_->WaitForGather();
+ }
+
+ static void TearDownTestCase() {
+ }
+
+ void CreateOffer(OfferOptions& options, uint32_t offerFlags) {
+ EnsureInit();
+ a1_->CreateOffer(options, offerFlags);
+ }
+
+ void CreateSetOffer(OfferOptions& options) {
+ EnsureInit();
+ a1_->CreateOffer(options, OFFER_AV);
+ a1_->SetLocal(TestObserver::OFFER, a1_->offer());
+ }
+
+ // Home for checks that we cannot perform by inspecting the various signaling
+ // classes. We should endeavor to make this function disappear, since SDP
+ // checking does not belong in these tests. That's the job of
+ // jsep_session_unittest.
+ void SDPSanityCheck(const std::string& sdp, uint32_t flags, bool offer)
+ {
+ std::cout << "SDPSanityCheck flags for "
+ << (offer ? "offer" : "answer")
+ << " = " << std::hex << std::showbase
+ << flags << std::dec
+ << ((flags & HAS_ALL_CANDIDATES)?" HAS_ALL_CANDIDATES":"")
+ << std::endl;
+
+ if (flags & HAS_ALL_CANDIDATES) {
+ ASSERT_NE(std::string::npos, sdp.find("a=candidate"))
+ << "should have at least one candidate";
+ ASSERT_NE(std::string::npos, sdp.find("a=end-of-candidates"));
+ ASSERT_EQ(std::string::npos, sdp.find("c=IN IP4 0.0.0.0"));
+ }
+ }
+
+ void CheckPipelines()
+ {
+ std::cout << "Checking pipelines..." << std::endl;
+ for (auto it = a1_->mAddedTracks.begin();
+ it != a1_->mAddedTracks.end();
+ ++it) {
+ a1_->CheckLocalPipeline(it->first.streamId, it->first.trackId, it->second);
+ a2_->CheckRemotePipeline(it->first.streamId, it->first.trackId, it->second);
+ }
+
+ for (auto it = a2_->mAddedTracks.begin();
+ it != a2_->mAddedTracks.end();
+ ++it) {
+ a2_->CheckLocalPipeline(it->first.streamId, it->first.trackId, it->second);
+ a1_->CheckRemotePipeline(it->first.streamId, it->first.trackId, it->second);
+ }
+ std::cout << "Done checking pipelines." << std::endl;
+ }
+
+ void CheckStreams(SignalingAgent& sender, SignalingAgent& receiver)
+ {
+ for (auto it = sender.mAddedTracks.begin();
+ it != sender.mAddedTracks.end();
+ ++it) {
+ // No checking for video yet, since we don't have support for fake video
+ // here yet. (bug 1142320)
+ if (it->second == SdpMediaSection::kAudio) {
+ int sendExpect = sender.GetPacketsSent(it->first.streamId) + 2;
+ int receiveExpect = receiver.GetPacketsReceived(it->first.streamId) + 2;
+
+ // TODO: Once we support more than one of each track type per stream,
+ // this will need to be updated.
+ WAIT(sender.GetPacketsSent(it->first.streamId) >= sendExpect &&
+ receiver.GetPacketsReceived(it->first.streamId) >= receiveExpect,
+ kDefaultTimeout);
+ ASSERT_LE(sendExpect, sender.GetPacketsSent(it->first.streamId))
+ << "Local track " << it->first.streamId << "/" << it->first.trackId
+ << " is not sending audio segments.";
+ ASSERT_LE(receiveExpect, receiver.GetPacketsReceived(it->first.streamId))
+ << "Remote track " << it->first.streamId << "/" << it->first.trackId
+ << " is not receiving audio segments.";
+ }
+ }
+ }
+
+ void CheckStreams()
+ {
+ std::cout << "Checking streams..." << std::endl;
+ CheckStreams(*a1_, *a2_);
+ CheckStreams(*a2_, *a1_);
+ std::cout << "Done checking streams." << std::endl;
+ }
+
+ void Offer(OfferOptions& options,
+ uint32_t offerAnswerFlags,
+ TrickleType trickleType = BOTH_TRICKLE) {
+ EnsureInit();
+ a1_->CreateOffer(options, offerAnswerFlags);
+ bool trickle = !!(trickleType & OFFERER_TRICKLES);
+ if (!trickle) {
+ a1_->pObserver->trickleCandidates = false;
+ }
+ a2_->mRemoteDescriptionSet = false;
+ a1_->SetLocal(TestObserver::OFFER, a1_->offer());
+ if (!trickle) {
+ a1_->WaitForGather();
+ a1_->UpdateOffer();
+ SDPSanityCheck(a1_->getLocalDescription(), HAS_ALL_CANDIDATES, true);
+ }
+ a2_->SetRemote(TestObserver::OFFER, a1_->offer());
+ }
+
+ void Answer(OfferOptions& options,
+ uint32_t offerAnswerFlags,
+ TrickleType trickleType = BOTH_TRICKLE) {
+
+ a2_->CreateAnswer(offerAnswerFlags);
+ bool trickle = !!(trickleType & ANSWERER_TRICKLES);
+ if (!trickle) {
+ a2_->pObserver->trickleCandidates = false;
+ }
+ a1_->mRemoteDescriptionSet = false;
+ a2_->SetLocal(TestObserver::ANSWER, a2_->answer());
+ if (!trickle) {
+ a2_->WaitForGather();
+ a2_->UpdateAnswer();
+ SDPSanityCheck(a2_->getLocalDescription(), HAS_ALL_CANDIDATES, false);
+ }
+ a1_->SetRemote(TestObserver::ANSWER, a2_->answer());
+ }
+
+ void WaitForCompleted() {
+ ASSERT_TRUE_WAIT(a1_->IceCompleted() == true, kDefaultTimeout);
+ ASSERT_TRUE_WAIT(a2_->IceCompleted() == true, kDefaultTimeout);
+ }
+
+ void OfferAnswer(OfferOptions& options,
+ uint32_t offerAnswerFlags,
+ TrickleType trickleType = BOTH_TRICKLE) {
+ EnsureInit();
+ Offer(options, offerAnswerFlags, trickleType);
+ Answer(options, offerAnswerFlags, trickleType);
+ WaitForCompleted();
+ CheckPipelines();
+ CheckStreams();
+ }
+
+ void OfferAnswerTrickleChrome(OfferOptions& options,
+ uint32_t offerAnswerFlags) {
+ EnsureInit();
+ Offer(options, offerAnswerFlags);
+ Answer(options, offerAnswerFlags);
+ WaitForCompleted();
+ CheckPipelines();
+ CheckStreams();
+ }
+
+ void CreateOfferRemoveTrack(OfferOptions& options, bool videoTrack) {
+ EnsureInit();
+ OfferOptions aoptions;
+ aoptions.setInt32Option("OfferToReceiveAudio", 1);
+ aoptions.setInt32Option("OfferToReceiveVideo", 1);
+ a1_->CreateOffer(aoptions, OFFER_AV);
+ a1_->CreateOfferRemoveTrack(options, videoTrack);
+ }
+
+ void CreateOfferAudioOnly(OfferOptions& options) {
+ EnsureInit();
+ a1_->CreateOffer(options, OFFER_AUDIO);
+ }
+
+ void CreateOfferAddCandidate(OfferOptions& options,
+ const std::string& candidate, const std::string& mid,
+ unsigned short level) {
+ EnsureInit();
+ a1_->CreateOffer(options, OFFER_AV);
+ a1_->AddIceCandidate(candidate, mid, level, true);
+ }
+
+ void AddIceCandidateEarly(const std::string& candidate, const std::string& mid,
+ unsigned short level) {
+ EnsureInit();
+ a1_->AddIceCandidate(candidate, mid, level, false);
+ }
+
+ std::string SwapMsids(const std::string& sdp, bool swapVideo) const
+ {
+ SipccSdpParser parser;
+ UniquePtr<Sdp> parsed = parser.Parse(sdp);
+
+ SdpMediaSection* previousMsection = nullptr;
+ bool swapped = false;
+ for (size_t i = 0; i < parsed->GetMediaSectionCount(); ++i) {
+ SdpMediaSection* currentMsection = &parsed->GetMediaSection(i);
+ bool isVideo = currentMsection->GetMediaType() == SdpMediaSection::kVideo;
+ if (swapVideo == isVideo) {
+ if (previousMsection) {
+ UniquePtr<SdpMsidAttributeList> prevMsid(
+ new SdpMsidAttributeList(
+ previousMsection->GetAttributeList().GetMsid()));
+ UniquePtr<SdpMsidAttributeList> currMsid(
+ new SdpMsidAttributeList(
+ currentMsection->GetAttributeList().GetMsid()));
+ previousMsection->GetAttributeList().SetAttribute(currMsid.release());
+ currentMsection->GetAttributeList().SetAttribute(prevMsid.release());
+ swapped = true;
+ }
+ previousMsection = currentMsection;
+ }
+ }
+
+ EXPECT_TRUE(swapped);
+
+ return parsed->ToString();
+ }
+
+ void CheckRtcpFbSdp(const std::string &sdp,
+ const std::set<std::string>& expected) {
+
+ std::set<std::string>::const_iterator it;
+
+ // Iterate through the list of expected feedback types and ensure
+ // that none of them are missing.
+ for (it = expected.begin(); it != expected.end(); ++it) {
+ std::string attr = std::string("\r\na=rtcp-fb:120 ") + (*it) + "\r\n";
+ std::cout << " - Checking for a=rtcp-fb: '" << *it << "'" << std::endl;
+ ASSERT_NE(sdp.find(attr), std::string::npos);
+ }
+
+ // Iterate through all of the rtcp-fb lines in the SDP and ensure
+ // that all of them are expected.
+ ParsedSDP sdpWrapper(sdp);
+ std::vector<std::string> values = sdpWrapper.GetLines("a=rtcp-fb:120");
+ std::vector<std::string>::iterator it2;
+ for (it2 = values.begin(); it2 != values.end(); ++it2) {
+ std::cout << " - Verifying that rtcp-fb is okay: '" << *it2
+ << "'" << std::endl;
+ ASSERT_NE(0U, expected.count(*it2));
+ }
+ }
+
+ std::string HardcodeRtcpFb(const std::string& sdp,
+ const std::set<std::string>& feedback) {
+ ParsedSDP sdpWrapper(sdp);
+
+ // Strip out any existing rtcp-fb lines
+ sdpWrapper.DeleteLines("a=rtcp-fb:120");
+ sdpWrapper.DeleteLines("a=rtcp-fb:126");
+ sdpWrapper.DeleteLines("a=rtcp-fb:97");
+
+ // Add rtcp-fb lines for the desired feedback types
+ // We know that the video section is generated second (last),
+ // so appending these to the end of the SDP has the desired effect.
+ std::set<std::string>::const_iterator it;
+ for (it = feedback.begin(); it != feedback.end(); ++it) {
+ sdpWrapper.AddLine(std::string("a=rtcp-fb:120 ") + (*it) + "\r\n");
+ sdpWrapper.AddLine(std::string("a=rtcp-fb:126 ") + (*it) + "\r\n");
+ sdpWrapper.AddLine(std::string("a=rtcp-fb:97 ") + (*it) + "\r\n");
+ }
+
+ std::cout << "Modified SDP " << std::endl
+ << indent(sdpWrapper.getSdp()) << std::endl;
+
+ // Double-check that the offered SDP matches what we expect
+ CheckRtcpFbSdp(sdpWrapper.getSdp(), feedback);
+
+ return sdpWrapper.getSdp();
+ }
+
+ void TestRtcpFbAnswer(const std::set<std::string>& feedback,
+ bool expectNack,
+ VideoSessionConduit::FrameRequestType frameRequestType) {
+ EnsureInit();
+ OfferOptions options;
+
+ a1_->CreateOffer(options, OFFER_AV);
+ a1_->SetLocal(TestObserver::OFFER, a1_->offer());
+
+ a2_->SetRemote(TestObserver::OFFER, a1_->offer());
+ a2_->CreateAnswer(OFFER_AV | ANSWER_AV);
+
+ a2_->SetLocal(TestObserver::ANSWER, a2_->answer());
+
+ std::string modifiedAnswer(HardcodeRtcpFb(a2_->answer(), feedback));
+
+ a1_->SetRemote(TestObserver::ANSWER, modifiedAnswer);
+
+ a1_->SetExpectedFrameRequestType(frameRequestType);
+ a1_->mExpectNack = expectNack;
+ // Since we don't support rewriting rtcp-fb in answers, a2 still thinks it
+ // will be doing all of the normal rtcp-fb
+
+ WaitForCompleted();
+ CheckPipelines();
+
+ CloseStreams();
+ }
+
+ void TestRtcpFbOffer(
+ const std::set<std::string>& feedback,
+ bool expectNack,
+ VideoSessionConduit::FrameRequestType frameRequestType) {
+ EnsureInit();
+ OfferOptions options;
+
+ a1_->CreateOffer(options, OFFER_AV);
+ a1_->SetLocal(TestObserver::OFFER, a1_->offer());
+
+ std::string modifiedOffer = HardcodeRtcpFb(a1_->offer(), feedback);
+
+ a2_->SetRemote(TestObserver::OFFER, modifiedOffer);
+ a1_->SetExpectedFrameRequestType(frameRequestType);
+ a1_->mExpectNack = expectNack;
+ a2_->SetExpectedFrameRequestType(frameRequestType);
+ a2_->mExpectNack = expectNack;
+
+ a2_->CreateAnswer(OFFER_AV | ANSWER_AV);
+
+ a2_->SetLocal(TestObserver::ANSWER, a2_->answer());
+ a1_->SetRemote(TestObserver::ANSWER, a2_->answer());
+
+ WaitForCompleted();
+
+ CheckPipelines();
+ CloseStreams();
+ }
+
+ void SetTestStunServer() {
+ stun_addr_ = TestStunServer::GetInstance()->addr();
+ stun_port_ = TestStunServer::GetInstance()->port();
+
+ TestStunServer::GetInstance()->SetActive(false);
+ TestStunServer::GetInstance()->SetResponseAddr(
+ kBogusSrflxAddress, kBogusSrflxPort);
+ }
+
+ // Check max-fs and max-fr in SDP
+ void CheckMaxFsFrSdp(const std::string sdp,
+ int format,
+ int max_fs,
+ int max_fr) {
+ ParsedSDP sdpWrapper(sdp);
+ std::stringstream ss;
+ ss << "a=fmtp:" << format;
+ std::vector<std::string> lines = sdpWrapper.GetLines(ss.str());
+
+ // Both max-fs and max-fr not exist
+ if (lines.empty()) {
+ ASSERT_EQ(max_fs, 0);
+ ASSERT_EQ(max_fr, 0);
+ return;
+ }
+
+ // At most one instance allowed for each format
+ ASSERT_EQ(lines.size(), 1U);
+
+ std::string line = lines.front();
+
+ // Make sure that max-fs doesn't exist
+ if (max_fs == 0) {
+ ASSERT_EQ(line.find("max-fs="), std::string::npos);
+ }
+ // Check max-fs value
+ if (max_fs > 0) {
+ std::stringstream ss;
+ ss << "max-fs=" << max_fs;
+ ASSERT_NE(line.find(ss.str()), std::string::npos);
+ }
+ // Make sure that max-fr doesn't exist
+ if (max_fr == 0) {
+ ASSERT_EQ(line.find("max-fr="), std::string::npos);
+ }
+ // Check max-fr value
+ if (max_fr > 0) {
+ std::stringstream ss;
+ ss << "max-fr=" << max_fr;
+ ASSERT_NE(line.find(ss.str()), std::string::npos);
+ }
+ }
+
+ void CloseStreams()
+ {
+ a1_->CloseSendStreams();
+ a2_->CloseSendStreams();
+ a1_->CloseReceiveStreams();
+ a2_->CloseReceiveStreams();
+ }
+
+ protected:
+ bool init_;
+ UniquePtr<SignalingAgent> a1_; // Canonically "caller"
+ UniquePtr<SignalingAgent> a2_; // Canonically "callee"
+ std::string stun_addr_;
+ uint16_t stun_port_;
+};
+
+static void SetIntPrefOnMainThread(nsCOMPtr<nsIPrefBranch> prefs,
+ const char *pref_name,
+ int new_value) {
+ MOZ_ASSERT(NS_IsMainThread());
+ prefs->SetIntPref(pref_name, new_value);
+}
+
+static void SetMaxFsFr(nsCOMPtr<nsIPrefBranch> prefs,
+ int max_fs,
+ int max_fr) {
+ gMainThread->Dispatch(
+ WrapRunnableNM(SetIntPrefOnMainThread,
+ prefs,
+ "media.navigator.video.max_fs",
+ max_fs),
+ NS_DISPATCH_SYNC);
+
+ gMainThread->Dispatch(
+ WrapRunnableNM(SetIntPrefOnMainThread,
+ prefs,
+ "media.navigator.video.max_fr",
+ max_fr),
+ NS_DISPATCH_SYNC);
+}
+
+class FsFrPrefClearer {
+ public:
+ explicit FsFrPrefClearer(nsCOMPtr<nsIPrefBranch> prefs): mPrefs(prefs) {}
+ ~FsFrPrefClearer() {
+ gMainThread->Dispatch(
+ WrapRunnableNM(FsFrPrefClearer::ClearUserPrefOnMainThread,
+ mPrefs,
+ "media.navigator.video.max_fs"),
+ NS_DISPATCH_SYNC);
+ gMainThread->Dispatch(
+ WrapRunnableNM(FsFrPrefClearer::ClearUserPrefOnMainThread,
+ mPrefs,
+ "media.navigator.video.max_fr"),
+ NS_DISPATCH_SYNC);
+ }
+
+ static void ClearUserPrefOnMainThread(nsCOMPtr<nsIPrefBranch> prefs,
+ const char *pref_name) {
+ MOZ_ASSERT(NS_IsMainThread());
+ prefs->ClearUserPref(pref_name);
+ }
+ private:
+ nsCOMPtr<nsIPrefBranch> mPrefs;
+};
+
+TEST_P(SignalingTest, JustInit)
+{
+}
+
+TEST_P(SignalingTest, CreateSetOffer)
+{
+ OfferOptions options;
+ CreateSetOffer(options);
+}
+
+TEST_P(SignalingTest, CreateOfferAudioVideoOptionUndefined)
+{
+ OfferOptions options;
+ CreateOffer(options, OFFER_AV);
+}
+
+TEST_P(SignalingTest, CreateOfferNoVideoStreamRecvVideo)
+{
+ OfferOptions options;
+ options.setInt32Option("OfferToReceiveAudio", 1);
+ options.setInt32Option("OfferToReceiveVideo", 1);
+ CreateOffer(options, OFFER_AUDIO);
+}
+
+TEST_P(SignalingTest, CreateOfferNoAudioStreamRecvAudio)
+{
+ OfferOptions options;
+ options.setInt32Option("OfferToReceiveAudio", 1);
+ options.setInt32Option("OfferToReceiveVideo", 1);
+ CreateOffer(options, OFFER_VIDEO);
+}
+
+TEST_P(SignalingTest, CreateOfferNoVideoStream)
+{
+ OfferOptions options;
+ options.setInt32Option("OfferToReceiveAudio", 1);
+ options.setInt32Option("OfferToReceiveVideo", 0);
+ CreateOffer(options, OFFER_AUDIO);
+}
+
+TEST_P(SignalingTest, CreateOfferNoAudioStream)
+{
+ OfferOptions options;
+ options.setInt32Option("OfferToReceiveAudio", 0);
+ options.setInt32Option("OfferToReceiveVideo", 1);
+ CreateOffer(options, OFFER_VIDEO);
+}
+
+TEST_P(SignalingTest, CreateOfferDontReceiveAudio)
+{
+ OfferOptions options;
+ options.setInt32Option("OfferToReceiveAudio", 0);
+ options.setInt32Option("OfferToReceiveVideo", 1);
+ CreateOffer(options, OFFER_AV);
+}
+
+TEST_P(SignalingTest, CreateOfferDontReceiveVideo)
+{
+ OfferOptions options;
+ options.setInt32Option("OfferToReceiveAudio", 1);
+ options.setInt32Option("OfferToReceiveVideo", 0);
+ CreateOffer(options, OFFER_AV);
+}
+
+TEST_P(SignalingTest, CreateOfferRemoveAudioTrack)
+{
+ OfferOptions options;
+ options.setInt32Option("OfferToReceiveAudio", 1);
+ options.setInt32Option("OfferToReceiveVideo", 1);
+ CreateOfferRemoveTrack(options, false);
+}
+
+TEST_P(SignalingTest, CreateOfferDontReceiveAudioRemoveAudioTrack)
+{
+ OfferOptions options;
+ options.setInt32Option("OfferToReceiveAudio", 0);
+ options.setInt32Option("OfferToReceiveVideo", 1);
+ CreateOfferRemoveTrack(options, false);
+}
+
+TEST_P(SignalingTest, CreateOfferDontReceiveVideoRemoveVideoTrack)
+{
+ OfferOptions options;
+ options.setInt32Option("OfferToReceiveAudio", 1);
+ options.setInt32Option("OfferToReceiveVideo", 0);
+ CreateOfferRemoveTrack(options, true);
+}
+
+TEST_P(SignalingTest, OfferAnswerNothingDisabled)
+{
+ OfferOptions options;
+ OfferAnswer(options, OFFER_AV | ANSWER_AV);
+}
+
+TEST_P(SignalingTest, OfferAnswerNoTrickle)
+{
+ OfferOptions options;
+ OfferAnswer(options, OFFER_AV | ANSWER_AV, NO_TRICKLE);
+}
+
+TEST_P(SignalingTest, OfferAnswerOffererTrickles)
+{
+ OfferOptions options;
+ OfferAnswer(options, OFFER_AV | ANSWER_AV, OFFERER_TRICKLES);
+}
+
+TEST_P(SignalingTest, OfferAnswerAnswererTrickles)
+{
+ OfferOptions options;
+ OfferAnswer(options, OFFER_AV | ANSWER_AV, ANSWERER_TRICKLES);
+}
+
+TEST_P(SignalingTest, OfferAnswerBothTrickle)
+{
+ OfferOptions options;
+ OfferAnswer(options, OFFER_AV | ANSWER_AV, BOTH_TRICKLE);
+}
+
+TEST_P(SignalingTest, OfferAnswerAudioBothTrickle)
+{
+ OfferOptions options;
+ OfferAnswer(options, OFFER_AUDIO | ANSWER_AUDIO, BOTH_TRICKLE);
+}
+
+
+TEST_P(SignalingTest, OfferAnswerNothingDisabledFullCycle)
+{
+ OfferOptions options;
+ OfferAnswer(options, OFFER_AV | ANSWER_AV);
+ // verify the default codec priorities
+ ASSERT_NE(a1_->getLocalDescription().find("UDP/TLS/RTP/SAVPF 109 9 0 8\r"),
+ std::string::npos);
+ ASSERT_NE(a2_->getLocalDescription().find("UDP/TLS/RTP/SAVPF 109\r"),
+ std::string::npos);
+}
+
+TEST_P(SignalingTest, OfferAnswerAudioInactive)
+{
+ OfferOptions options;
+ options.setInt32Option("OfferToReceiveAudio", 1);
+ options.setInt32Option("OfferToReceiveVideo", 1);
+ OfferAnswer(options, OFFER_VIDEO | ANSWER_VIDEO);
+}
+
+TEST_P(SignalingTest, OfferAnswerVideoInactive)
+{
+ OfferOptions options;
+ options.setInt32Option("OfferToReceiveAudio", 1);
+ options.setInt32Option("OfferToReceiveVideo", 1);
+ OfferAnswer(options, OFFER_AUDIO | ANSWER_AUDIO);
+ CloseStreams();
+}
+
+TEST_P(SignalingTest, CreateOfferAddCandidate)
+{
+ OfferOptions options;
+ CreateOfferAddCandidate(options, strSampleCandidate,
+ strSampleMid, nSamplelevel);
+}
+
+TEST_P(SignalingTest, AddIceCandidateEarly)
+{
+ OfferOptions options;
+ AddIceCandidateEarly(strSampleCandidate,
+ strSampleMid, nSamplelevel);
+}
+
+TEST_P(SignalingTest, OfferAnswerDontAddAudioStreamOnAnswerNoOptions)
+{
+ OfferOptions options;
+ options.setInt32Option("OfferToReceiveAudio", 1);
+ options.setInt32Option("OfferToReceiveVideo", 1);
+ OfferAnswer(options, OFFER_AV | ANSWER_VIDEO);
+}
+
+TEST_P(SignalingTest, OfferAnswerDontAddVideoStreamOnAnswerNoOptions)
+{
+ OfferOptions options;
+ options.setInt32Option("OfferToReceiveAudio", 1);
+ options.setInt32Option("OfferToReceiveVideo", 1);
+ OfferAnswer(options, OFFER_AV | ANSWER_AUDIO);
+}
+
+TEST_P(SignalingTest, OfferAnswerDontAddAudioVideoStreamsOnAnswerNoOptions)
+{
+ OfferOptions options;
+ options.setInt32Option("OfferToReceiveAudio", 1);
+ options.setInt32Option("OfferToReceiveVideo", 1);
+ OfferAnswer(options, OFFER_AV | ANSWER_NONE);
+}
+
+TEST_P(SignalingTest, RenegotiationOffererAddsTracks)
+{
+ OfferOptions options;
+ OfferAnswer(options, OFFER_AV | ANSWER_AV);
+ // OFFER_AV causes a new stream + tracks to be added
+ OfferAnswer(options, OFFER_AV);
+ CloseStreams();
+}
+
+TEST_P(SignalingTest, RenegotiationOffererRemovesTrack)
+{
+ OfferOptions options;
+ OfferAnswer(options, OFFER_AV | ANSWER_AV);
+
+ a1_->RemoveTrack(0, false);
+
+ OfferAnswer(options, OFFER_NONE);
+
+ CloseStreams();
+}
+
+TEST_P(SignalingTest, RenegotiationBothRemoveThenAddTrack)
+{
+ OfferOptions options;
+ OfferAnswer(options, OFFER_AV | ANSWER_AV);
+
+ a1_->RemoveTrack(0, false);
+ a2_->RemoveTrack(0, false);
+
+ OfferAnswer(options, OFFER_NONE);
+
+ // OFFER_AUDIO causes a new audio track to be added on both sides
+ OfferAnswer(options, OFFER_AUDIO);
+
+ CloseStreams();
+}
+
+TEST_P(SignalingTest, RenegotiationOffererReplacesTrack)
+{
+ OfferOptions options;
+ OfferAnswer(options, OFFER_AV | ANSWER_AV);
+
+ a1_->RemoveTrack(0, false);
+
+ // OFFER_AUDIO causes a new audio track to be added on both sides
+ OfferAnswer(options, OFFER_AUDIO);
+
+ CloseStreams();
+}
+
+TEST_P(SignalingTest, RenegotiationOffererSwapsMsids)
+{
+ OfferOptions options;
+
+ EnsureInit();
+ a1_->AddStream(DOMMediaStream::HINT_CONTENTS_AUDIO |
+ DOMMediaStream::HINT_CONTENTS_VIDEO);
+
+ OfferAnswer(options, OFFER_AV | ANSWER_AV);
+
+ a1_->CreateOffer(options, OFFER_NONE);
+ a1_->SetLocal(TestObserver::OFFER, a1_->offer());
+ std::string audioSwapped = SwapMsids(a1_->offer(), false);
+ std::string audioAndVideoSwapped = SwapMsids(audioSwapped, true);
+ std::cout << "Msids swapped: " << std::endl << audioAndVideoSwapped << std::endl;
+ a2_->SetRemote(TestObserver::OFFER, audioAndVideoSwapped);
+ Answer(options, OFFER_NONE, BOTH_TRICKLE);
+ WaitForCompleted();
+
+ CheckPipelines();
+ CheckStreams();
+
+ CloseStreams();
+}
+
+TEST_P(SignalingTest, RenegotiationAnswererAddsTracks)
+{
+ OfferOptions options;
+ OfferAnswer(options, OFFER_AV | ANSWER_AV);
+
+ options.setInt32Option("OfferToReceiveAudio", 2);
+ options.setInt32Option("OfferToReceiveVideo", 2);
+
+ // ANSWER_AV causes a new stream + tracks to be added
+ OfferAnswer(options, ANSWER_AV);
+
+ CloseStreams();
+}
+
+TEST_P(SignalingTest, RenegotiationAnswererRemovesTrack)
+{
+ OfferOptions options;
+ OfferAnswer(options, OFFER_AV | ANSWER_AV);
+
+ a2_->RemoveTrack(0, false);
+
+ OfferAnswer(options, OFFER_NONE);
+
+ CloseStreams();
+}
+
+TEST_P(SignalingTest, RenegotiationAnswererReplacesTrack)
+{
+ OfferOptions options;
+ OfferAnswer(options, OFFER_AV | ANSWER_AV);
+
+ a2_->RemoveTrack(0, false);
+
+ // ANSWER_AUDIO causes a new audio track to be added
+ OfferAnswer(options, ANSWER_AUDIO);
+
+ CloseStreams();
+}
+
+TEST_P(SignalingTest, BundleRenegotiation)
+{
+ OfferOptions options;
+ OfferAnswer(options, OFFER_AV | ANSWER_AV);
+
+ // If we did bundle before, turn it off, if not, turn it on
+ if (a1_->mBundleEnabled && a2_->mBundleEnabled) {
+ a1_->SetBundleEnabled(false);
+ } else {
+ a1_->SetBundleEnabled(true);
+ a2_->SetBundleEnabled(true);
+ }
+
+ OfferAnswer(options, OFFER_NONE);
+}
+
+TEST_P(SignalingTest, FullCallAudioOnly)
+{
+ OfferOptions options;
+ OfferAnswer(options, OFFER_AUDIO | ANSWER_AUDIO);
+
+ CloseStreams();
+}
+
+TEST_P(SignalingTest, FullCallVideoOnly)
+{
+ OfferOptions options;
+ OfferAnswer(options, OFFER_VIDEO | ANSWER_VIDEO);
+
+ CloseStreams();
+}
+
+TEST_P(SignalingTest, OfferAndAnswerWithExtraCodec)
+{
+ EnsureInit();
+ OfferOptions options;
+ Offer(options, OFFER_AUDIO);
+
+ a2_->CreateAnswer(OFFER_AUDIO | ANSWER_AUDIO);
+ a2_->SetLocal(TestObserver::ANSWER, a2_->answer());
+ ParsedSDP sdpWrapper(a2_->answer());
+ sdpWrapper.ReplaceLine("m=audio",
+ "m=audio 65375 UDP/TLS/RTP/SAVPF 109 8\r\n");
+ sdpWrapper.AddLine("a=rtpmap:8 PCMA/8000\r\n");
+ std::cout << "Modified SDP " << std::endl
+ << indent(sdpWrapper.getSdp()) << std::endl;
+
+ a1_->SetRemote(TestObserver::ANSWER, sdpWrapper.getSdp());
+
+ WaitForCompleted();
+
+ CheckPipelines();
+ CheckStreams();
+
+ CloseStreams();
+}
+
+TEST_P(SignalingTest, FullCallTrickle)
+{
+ OfferOptions options;
+ OfferAnswer(options, OFFER_AV | ANSWER_AV);
+
+ std::cerr << "ICE handshake completed" << std::endl;
+
+ CloseStreams();
+}
+
+// Offer answer with trickle but with chrome-style candidates
+TEST_P(SignalingTest, DISABLED_FullCallTrickleChrome)
+{
+ OfferOptions options;
+ OfferAnswerTrickleChrome(options, OFFER_AV | ANSWER_AV);
+
+ std::cerr << "ICE handshake completed" << std::endl;
+
+ CloseStreams();
+}
+
+TEST_P(SignalingTest, FullCallTrickleBeforeSetLocal)
+{
+ OfferOptions options;
+ Offer(options, OFFER_AV | ANSWER_AV);
+ // ICE will succeed even if one side fails to trickle, so we need to disable
+ // one side before performing a test that might cause candidates to be
+ // dropped
+ a2_->DropOutgoingTrickleCandidates();
+ // Wait until all of a1's candidates have been trickled to a2, _before_ a2
+ // has called CreateAnswer/SetLocal (ie; the ICE stack is not running yet)
+ a1_->WaitForGather();
+ Answer(options, OFFER_AV | ANSWER_AV);
+ WaitForCompleted();
+
+ CheckPipelines();
+ CheckStreams();
+
+ std::cerr << "ICE handshake completed" << std::endl;
+
+ CloseStreams();
+}
+
+// This test comes from Bug 810220
+// TODO: Move this to jsep_session_unittest
+TEST_P(SignalingTest, AudioOnlyG711Call)
+{
+ EnsureInit();
+
+ OfferOptions options;
+ const std::string& offer(strG711SdpOffer);
+
+ std::cout << "Setting offer to:" << std::endl << indent(offer) << std::endl;
+ a2_->SetRemote(TestObserver::OFFER, offer);
+
+ std::cout << "Creating answer:" << std::endl;
+ a2_->CreateAnswer(OFFER_AUDIO | ANSWER_AUDIO);
+
+ std::string answer = a2_->answer();
+
+ // They didn't offer opus, so our answer shouldn't include it.
+ ASSERT_EQ(answer.find(" opus/"), std::string::npos);
+
+ // They also didn't offer video or application
+ ASSERT_EQ(answer.find("video"), std::string::npos);
+ ASSERT_EQ(answer.find("application"), std::string::npos);
+
+ // We should answer with PCMU and telephone-event
+ ASSERT_NE(answer.find(" PCMU/8000"), std::string::npos);
+
+ // Double-check the directionality
+ ASSERT_NE(answer.find("\r\na=sendrecv"), std::string::npos);
+
+}
+
+TEST_P(SignalingTest, IncomingOfferIceLite)
+{
+ EnsureInit();
+
+ std::string offer =
+ "v=0\r\n"
+ "o=- 1936463 1936463 IN IP4 148.147.200.251\r\n"
+ "s=-\r\n"
+ "c=IN IP4 148.147.200.251\r\n"
+ "t=0 0\r\n"
+ "a=ice-lite\r\n"
+ "a=fingerprint:sha-1 "
+ "E7:FA:17:DA:3F:3C:1E:D8:E4:9C:8C:4C:13:B9:2E:D5:C6:78:AB:B3\r\n"
+ "m=audio 40014 UDP/TLS/RTP/SAVPF 8 0 101\r\n"
+ "a=rtpmap:8 PCMA/8000\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "a=rtpmap:101 telephone-event/8000\r\n"
+ "a=fmtp:101 0-15\r\n"
+ "a=ptime:20\r\n"
+ "a=sendrecv\r\n"
+ "a=ice-ufrag:bf2LAgqBZdiWFR2r\r\n"
+ "a=ice-pwd:ScxgaNzdBOYScR0ORleAvt1x\r\n"
+ "a=candidate:1661181211 1 udp 10 148.147.200.251 40014 typ host\r\n"
+ "a=candidate:1661181211 2 udp 9 148.147.200.251 40015 typ host\r\n"
+ "a=setup:actpass\r\n";
+
+ std::cout << "Setting offer to:" << std::endl << indent(offer) << std::endl;
+ a2_->SetRemote(TestObserver::OFFER, offer);
+
+ std::cout << "Creating answer:" << std::endl;
+ a2_->CreateAnswer(OFFER_AUDIO | ANSWER_AUDIO);
+ a2_->SetLocal(TestObserver::ANSWER, a2_->answer());
+
+ ASSERT_EQ(a2_->pc->media()->ice_ctx()->GetControlling(),
+ NrIceCtx::ICE_CONTROLLING);
+}
+
+// This test comes from Bug814038
+TEST_P(SignalingTest, ChromeOfferAnswer)
+{
+ EnsureInit();
+
+ // This is captured SDP from an early interop attempt with Chrome.
+ std::string offer =
+ "v=0\r\n"
+ "o=- 1713781661 2 IN IP4 127.0.0.1\r\n"
+ "s=-\r\n"
+ "t=0 0\r\n"
+ "a=group:BUNDLE audio video\r\n"
+
+ "m=audio 1 UDP/TLS/RTP/SAVPF 103 104 111 0 8 107 106 105 13 126\r\n"
+ "a=fingerprint:sha-1 4A:AD:B9:B1:3F:82:18:3B:54:02:12:DF:3E:"
+ "5D:49:6B:19:E5:7C:AB\r\n"
+ "a=setup:active\r\n"
+ "c=IN IP4 0.0.0.0\r\n"
+ "a=rtcp:1 IN IP4 0.0.0.0\r\n"
+ "a=ice-ufrag:lBrbdDfrVBH1cldN\r\n"
+ "a=ice-pwd:rzh23jet4QpCaEoj9Sl75pL3\r\n"
+ "a=ice-options:google-ice\r\n"
+ "a=sendrecv\r\n"
+ "a=mid:audio\r\n"
+ "a=rtcp-mux\r\n"
+ "a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:"
+ "RzrYlzpkTsvgYFD1hQqNCzQ7y4emNLKI1tODsjim\r\n"
+ "a=rtpmap:103 ISAC/16000\r\n"
+ "a=rtpmap:104 ISAC/32000\r\n"
+ // NOTE: the actual SDP that Chrome sends at the moment
+ // doesn't indicate two channels. I've amended their SDP
+ // here, under the assumption that the constraints
+ // described in draft-spittka-payload-rtp-opus will
+ // eventually be implemented by Google.
+ "a=rtpmap:111 opus/48000/2\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "a=rtpmap:8 PCMA/8000\r\n"
+ "a=rtpmap:107 CN/48000\r\n"
+ "a=rtpmap:106 CN/32000\r\n"
+ "a=rtpmap:105 CN/16000\r\n"
+ "a=rtpmap:13 CN/8000\r\n"
+ "a=rtpmap:126 telephone-event/8000\r\n"
+ "a=ssrc:661333377 cname:KIXaNxUlU5DP3fVS\r\n"
+ "a=ssrc:661333377 msid:A5UL339RyGxT7zwgyF12BFqesxkmbUsaycp5 a0\r\n"
+ "a=ssrc:661333377 mslabel:A5UL339RyGxT7zwgyF12BFqesxkmbUsaycp5\r\n"
+ "a=ssrc:661333377 label:A5UL339RyGxT7zwgyF12BFqesxkmbUsaycp5a0\r\n"
+
+ "m=video 1 UDP/TLS/RTP/SAVPF 100 101 102\r\n"
+ "a=fingerprint:sha-1 4A:AD:B9:B1:3F:82:18:3B:54:02:12:DF:3E:5D:49:"
+ "6B:19:E5:7C:AB\r\n"
+ "a=setup:active\r\n"
+ "c=IN IP4 0.0.0.0\r\n"
+ "a=rtcp:1 IN IP4 0.0.0.0\r\n"
+ "a=ice-ufrag:lBrbdDfrVBH1cldN\r\n"
+ "a=ice-pwd:rzh23jet4QpCaEoj9Sl75pL3\r\n"
+ "a=ice-options:google-ice\r\n"
+ "a=sendrecv\r\n"
+ "a=mid:video\r\n"
+ "a=rtcp-mux\r\n"
+ "a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:"
+ "RzrYlzpkTsvgYFD1hQqNCzQ7y4emNLKI1tODsjim\r\n"
+ "a=rtpmap:100 VP8/90000\r\n"
+ "a=rtpmap:101 red/90000\r\n"
+ "a=rtpmap:102 ulpfec/90000\r\n"
+ "a=rtcp-fb:100 nack\r\n"
+ "a=rtcp-fb:100 ccm fir\r\n"
+ "a=ssrc:3012607008 cname:KIXaNxUlU5DP3fVS\r\n"
+ "a=ssrc:3012607008 msid:A5UL339RyGxT7zwgyF12BFqesxkmbUsaycp5 v0\r\n"
+ "a=ssrc:3012607008 mslabel:A5UL339RyGxT7zwgyF12BFqesxkmbUsaycp5\r\n"
+ "a=ssrc:3012607008 label:A5UL339RyGxT7zwgyF12BFqesxkmbUsaycp5v0\r\n";
+
+
+ std::cout << "Setting offer to:" << std::endl << indent(offer) << std::endl;
+ a2_->SetRemote(TestObserver::OFFER, offer);
+
+ std::cout << "Creating answer:" << std::endl;
+ a2_->CreateAnswer(OFFER_AUDIO | ANSWER_AUDIO);
+
+ std::string answer = a2_->answer();
+}
+
+
+TEST_P(SignalingTest, FullChromeHandshake)
+{
+ EnsureInit();
+
+ std::string offer = "v=0\r\n"
+ "o=- 3835809413 2 IN IP4 127.0.0.1\r\n"
+ "s=-\r\n"
+ "t=0 0\r\n"
+ "a=group:BUNDLE audio video\r\n"
+ "a=msid-semantic: WMS ahheYQXHFU52slYMrWNtKUyHCtWZsOJgjlOH\r\n"
+ "m=audio 1 UDP/TLS/RTP/SAVPF 103 104 111 0 8 107 106 105 13 126\r\n"
+ "c=IN IP4 1.1.1.1\r\n"
+ "a=rtcp:1 IN IP4 1.1.1.1\r\n"
+ "a=ice-ufrag:jz9UBk9RT8eCQXiL\r\n"
+ "a=ice-pwd:iscXxsdU+0gracg0g5D45orx\r\n"
+ "a=ice-options:google-ice\r\n"
+ "a=fingerprint:sha-256 A8:76:8C:4C:FA:2E:67:D7:F8:1D:28:4E:90:24:04:"
+ "12:EB:B4:A6:69:3D:05:92:E4:91:C3:EA:F9:B7:54:D3:09\r\n"
+ "a=setup:active\r\n"
+ "a=sendrecv\r\n"
+ "a=mid:audio\r\n"
+ "a=rtcp-mux\r\n"
+ "a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:/he/v44FKu/QvEhex86zV0pdn2V"
+ "4Y7wB2xaZ8eUy\r\n"
+ "a=rtpmap:103 ISAC/16000\r\n"
+ "a=rtpmap:104 ISAC/32000\r\n"
+ "a=rtpmap:111 opus/48000/2\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "a=rtpmap:8 PCMA/8000\r\n"
+ "a=rtpmap:107 CN/48000\r\n"
+ "a=rtpmap:106 CN/32000\r\n"
+ "a=rtpmap:105 CN/16000\r\n"
+ "a=rtpmap:13 CN/8000\r\n"
+ "a=rtpmap:126 telephone-event/8000\r\n"
+ "a=ssrc:3389377748 cname:G5I+Jxz4rcaq8IIK\r\n"
+ "a=ssrc:3389377748 msid:ahheYQXHFU52slYMrWNtKUyHCtWZsOJgjlOH a0\r\n"
+ "a=ssrc:3389377748 mslabel:ahheYQXHFU52slYMrWNtKUyHCtWZsOJgjlOH\r\n"
+ "a=ssrc:3389377748 label:ahheYQXHFU52slYMrWNtKUyHCtWZsOJgjlOHa0\r\n"
+ "m=video 1 UDP/TLS/RTP/SAVPF 100 116 117\r\n"
+ "c=IN IP4 1.1.1.1\r\n"
+ "a=rtcp:1 IN IP4 1.1.1.1\r\n"
+ "a=ice-ufrag:jz9UBk9RT8eCQXiL\r\n"
+ "a=ice-pwd:iscXxsdU+0gracg0g5D45orx\r\n"
+ "a=ice-options:google-ice\r\n"
+ "a=fingerprint:sha-256 A8:76:8C:4C:FA:2E:67:D7:F8:1D:28:4E:90:24:04:"
+ "12:EB:B4:A6:69:3D:05:92:E4:91:C3:EA:F9:B7:54:D3:09\r\n"
+ "a=setup:active\r\n"
+ "a=sendrecv\r\n"
+ "a=mid:video\r\n"
+ "a=rtcp-mux\r\n"
+ "a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:/he/v44FKu/QvEhex86zV0pdn2V"
+ "4Y7wB2xaZ8eUy\r\n"
+ "a=rtpmap:100 VP8/90000\r\n"
+ "a=rtpmap:116 red/90000\r\n"
+ "a=rtpmap:117 ulpfec/90000\r\n"
+ "a=ssrc:3613537198 cname:G5I+Jxz4rcaq8IIK\r\n"
+ "a=ssrc:3613537198 msid:ahheYQXHFU52slYMrWNtKUyHCtWZsOJgjlOH v0\r\n"
+ "a=ssrc:3613537198 mslabel:ahheYQXHFU52slYMrWNtKUyHCtWZsOJgjlOH\r\n"
+ "a=ssrc:3613537198 label:ahheYQXHFU52slYMrWNtKUyHCtWZsOJgjlOHv0\r\n";
+
+ std::cout << "Setting offer to:" << std::endl << indent(offer) << std::endl;
+ a2_->SetRemote(TestObserver::OFFER, offer);
+
+ std::cout << "Creating answer:" << std::endl;
+ a2_->CreateAnswer(OFFER_AUDIO | ANSWER_AUDIO);
+
+ std::cout << "Setting answer" << std::endl;
+ a2_->SetLocal(TestObserver::ANSWER, a2_->answer());
+
+ std::string answer = a2_->answer();
+ ASSERT_NE(answer.find("111 opus/"), std::string::npos);
+}
+
+// Disabled pending resolution of bug 818640.
+// Actually, this test is completely broken; you can't just call
+// SetRemote/CreateAnswer over and over again.
+// If we were to test this sort of thing, it would belong in
+// jsep_session_unitest
+TEST_P(SignalingTest, DISABLED_OfferAllDynamicTypes)
+{
+ EnsureInit();
+
+ std::string offer;
+ for (int i = 96; i < 128; i++)
+ {
+ std::stringstream ss;
+ ss << i;
+ std::cout << "Trying dynamic pt = " << i << std::endl;
+ offer =
+ "v=0\r\n"
+ "o=- 1 1 IN IP4 148.147.200.251\r\n"
+ "s=-\r\n"
+ "b=AS:64\r\n"
+ "t=0 0\r\n"
+ "a=fingerprint:sha-256 F3:FA:20:C0:CD:48:C4:5F:02:5F:A5:D3:21:D0:2D:48:"
+ "7B:31:60:5C:5A:D8:0D:CD:78:78:6C:6D:CE:CC:0C:67\r\n"
+ "m=audio 9000 RTP/AVP " + ss.str() + "\r\n"
+ "c=IN IP4 148.147.200.251\r\n"
+ "b=TIAS:64000\r\n"
+ "a=rtpmap:" + ss.str() +" opus/48000/2\r\n"
+ "a=candidate:0 1 udp 2130706432 148.147.200.251 9000 typ host\r\n"
+ "a=candidate:0 2 udp 2130706432 148.147.200.251 9005 typ host\r\n"
+ "a=ice-ufrag:cYuakxkEKH+RApYE\r\n"
+ "a=ice-pwd:bwtpzLZD+3jbu8vQHvEa6Xuq\r\n"
+ "a=sendrecv\r\n";
+
+ /*
+ std::cout << "Setting offer to:" << std::endl
+ << indent(offer) << std::endl;
+ */
+ a2_->SetRemote(TestObserver::OFFER, offer);
+
+ //std::cout << "Creating answer:" << std::endl;
+ a2_->CreateAnswer(OFFER_AUDIO | ANSWER_AUDIO);
+
+ std::string answer = a2_->answer();
+
+ ASSERT_NE(answer.find(ss.str() + " opus/"), std::string::npos);
+ }
+
+}
+
+// TODO: Move to jsep_session_unittest
+TEST_P(SignalingTest, ipAddrAnyOffer)
+{
+ EnsureInit();
+
+ std::string offer =
+ "v=0\r\n"
+ "o=- 1 1 IN IP4 127.0.0.1\r\n"
+ "s=-\r\n"
+ "b=AS:64\r\n"
+ "t=0 0\r\n"
+ "a=fingerprint:sha-256 F3:FA:20:C0:CD:48:C4:5F:02:5F:A5:D3:21:D0:2D:48:"
+ "7B:31:60:5C:5A:D8:0D:CD:78:78:6C:6D:CE:CC:0C:67\r\n"
+ "m=audio 9000 RTP/AVP 99\r\n"
+ "c=IN IP4 0.0.0.0\r\n"
+ "a=rtpmap:99 opus/48000/2\r\n"
+ "a=ice-ufrag:cYuakxkEKH+RApYE\r\n"
+ "a=ice-pwd:bwtpzLZD+3jbu8vQHvEa6Xuq\r\n"
+ "a=setup:active\r\n"
+ "a=sendrecv\r\n";
+
+ a2_->SetRemote(TestObserver::OFFER, offer);
+ ASSERT_TRUE(a2_->pObserver->state == TestObserver::stateSuccess);
+ a2_->CreateAnswer(OFFER_AUDIO | ANSWER_AUDIO);
+ ASSERT_TRUE(a2_->pObserver->state == TestObserver::stateSuccess);
+ std::string answer = a2_->answer();
+ ASSERT_NE(answer.find("a=sendrecv"), std::string::npos);
+}
+
+static void CreateSDPForBigOTests(std::string& offer, const std::string& number) {
+ offer =
+ "v=0\r\n"
+ "o=- ";
+ offer += number;
+ offer += " ";
+ offer += number;
+ offer += " IN IP4 127.0.0.1\r\n"
+ "s=-\r\n"
+ "b=AS:64\r\n"
+ "t=0 0\r\n"
+ "a=fingerprint:sha-256 F3:FA:20:C0:CD:48:C4:5F:02:5F:A5:D3:21:D0:2D:48:"
+ "7B:31:60:5C:5A:D8:0D:CD:78:78:6C:6D:CE:CC:0C:67\r\n"
+ "m=audio 9000 RTP/AVP 99\r\n"
+ "c=IN IP4 0.0.0.0\r\n"
+ "a=rtpmap:99 opus/48000/2\r\n"
+ "a=ice-ufrag:cYuakxkEKH+RApYE\r\n"
+ "a=ice-pwd:bwtpzLZD+3jbu8vQHvEa6Xuq\r\n"
+ "a=setup:active\r\n"
+ "a=sendrecv\r\n";
+}
+
+// TODO: Move to jsep_session_unittest
+TEST_P(SignalingTest, BigOValues)
+{
+ EnsureInit();
+
+ std::string offer;
+
+ CreateSDPForBigOTests(offer, "12345678901234567");
+
+ a2_->SetRemote(TestObserver::OFFER, offer);
+ ASSERT_EQ(a2_->pObserver->state, TestObserver::stateSuccess);
+}
+
+// TODO: Move to jsep_session_unittest
+// We probably need to retain at least one test case for each API entry point
+// that verifies that errors are propagated correctly, though.
+TEST_P(SignalingTest, BigOValuesExtraChars)
+{
+ EnsureInit();
+
+ std::string offer;
+
+ CreateSDPForBigOTests(offer, "12345678901234567FOOBAR");
+
+ // The signaling state will remain "stable" because the unparsable
+ // SDP leads to a failure in SetRemoteDescription.
+ a2_->SetRemote(TestObserver::OFFER, offer, true,
+ PCImplSignalingState::SignalingStable);
+ ASSERT_TRUE(a2_->pObserver->state == TestObserver::stateError);
+}
+
+// TODO: Move to jsep_session_unittest
+TEST_P(SignalingTest, BigOValuesTooBig)
+{
+ EnsureInit();
+
+ std::string offer;
+
+ CreateSDPForBigOTests(offer, "18446744073709551615");
+
+ // The signaling state will remain "stable" because the unparsable
+ // SDP leads to a failure in SetRemoteDescription.
+ a2_->SetRemote(TestObserver::OFFER, offer, true,
+ PCImplSignalingState::SignalingStable);
+ ASSERT_TRUE(a2_->pObserver->state == TestObserver::stateError);
+}
+
+// TODO: Move to jsep_session_unittest
+TEST_P(SignalingTest, SetLocalAnswerInStable)
+{
+ EnsureInit();
+
+ OfferOptions options;
+ CreateOffer(options, OFFER_AUDIO);
+
+ // The signaling state will remain "stable" because the
+ // SetLocalDescription call fails.
+ a1_->SetLocal(TestObserver::ANSWER, a1_->offer(), true,
+ PCImplSignalingState::SignalingStable);
+ ASSERT_EQ(a1_->pObserver->lastStatusCode,
+ PeerConnectionImpl::kInvalidState);
+}
+
+// TODO: Move to jsep_session_unittest
+TEST_P(SignalingTest, SetRemoteAnswerInStable) {
+ EnsureInit();
+
+ // The signaling state will remain "stable" because the
+ // SetRemoteDescription call fails.
+ a1_->SetRemote(TestObserver::ANSWER, strSampleSdpAudioVideoNoIce, true,
+ PCImplSignalingState::SignalingStable);
+ ASSERT_EQ(a1_->pObserver->lastStatusCode,
+ PeerConnectionImpl::kInvalidState);
+}
+
+// TODO: Move to jsep_session_unittest
+TEST_P(SignalingTest, SetLocalAnswerInHaveLocalOffer) {
+ OfferOptions options;
+ CreateOffer(options, OFFER_AUDIO);
+ a1_->SetLocal(TestObserver::OFFER, a1_->offer());
+ ASSERT_EQ(a1_->pObserver->lastStatusCode,
+ PeerConnectionImpl::kNoError);
+
+ // The signaling state will remain "have-local-offer" because the
+ // SetLocalDescription call fails.
+ a1_->SetLocal(TestObserver::ANSWER, a1_->offer(), true,
+ PCImplSignalingState::SignalingHaveLocalOffer);
+ ASSERT_EQ(a1_->pObserver->lastStatusCode,
+ PeerConnectionImpl::kInvalidState);
+}
+
+// TODO: Move to jsep_session_unittest
+TEST_P(SignalingTest, SetRemoteOfferInHaveLocalOffer) {
+ OfferOptions options;
+ CreateOffer(options, OFFER_AUDIO);
+ a1_->SetLocal(TestObserver::OFFER, a1_->offer());
+ ASSERT_EQ(a1_->pObserver->lastStatusCode,
+ PeerConnectionImpl::kNoError);
+
+ // The signaling state will remain "have-local-offer" because the
+ // SetRemoteDescription call fails.
+ a1_->SetRemote(TestObserver::OFFER, a1_->offer(), true,
+ PCImplSignalingState::SignalingHaveLocalOffer);
+ ASSERT_EQ(a1_->pObserver->lastStatusCode,
+ PeerConnectionImpl::kInvalidState);
+}
+
+// TODO: Move to jsep_session_unittest
+TEST_P(SignalingTest, SetLocalOfferInHaveRemoteOffer) {
+ OfferOptions options;
+ CreateOffer(options, OFFER_AUDIO);
+ a2_->SetRemote(TestObserver::OFFER, a1_->offer());
+ ASSERT_EQ(a2_->pObserver->lastStatusCode,
+ PeerConnectionImpl::kNoError);
+
+ // The signaling state will remain "have-remote-offer" because the
+ // SetLocalDescription call fails.
+ a2_->SetLocal(TestObserver::OFFER, a1_->offer(), true,
+ PCImplSignalingState::SignalingHaveRemoteOffer);
+ ASSERT_EQ(a2_->pObserver->lastStatusCode,
+ PeerConnectionImpl::kInvalidState);
+}
+
+// TODO: Move to jsep_session_unittest
+TEST_P(SignalingTest, SetRemoteAnswerInHaveRemoteOffer) {
+ OfferOptions options;
+ CreateOffer(options, OFFER_AUDIO);
+ a2_->SetRemote(TestObserver::OFFER, a1_->offer());
+ ASSERT_EQ(a2_->pObserver->lastStatusCode,
+ PeerConnectionImpl::kNoError);
+
+ // The signaling state will remain "have-remote-offer" because the
+ // SetRemoteDescription call fails.
+ a2_->SetRemote(TestObserver::ANSWER, a1_->offer(), true,
+ PCImplSignalingState::SignalingHaveRemoteOffer);
+ ASSERT_EQ(a2_->pObserver->lastStatusCode,
+ PeerConnectionImpl::kInvalidState);
+}
+
+// Disabled until the spec adds a failure callback to addStream
+// Actually, this is allowed I think, it just triggers a negotiationneeded
+TEST_P(SignalingTest, DISABLED_AddStreamInHaveLocalOffer) {
+ OfferOptions options;
+ CreateOffer(options, OFFER_AUDIO);
+ a1_->SetLocal(TestObserver::OFFER, a1_->offer());
+ ASSERT_EQ(a1_->pObserver->lastStatusCode,
+ PeerConnectionImpl::kNoError);
+ a1_->AddStream();
+ ASSERT_EQ(a1_->pObserver->lastStatusCode,
+ PeerConnectionImpl::kInvalidState);
+}
+
+// Disabled until the spec adds a failure callback to removeStream
+// Actually, this is allowed I think, it just triggers a negotiationneeded
+TEST_P(SignalingTest, DISABLED_RemoveStreamInHaveLocalOffer) {
+ OfferOptions options;
+ CreateOffer(options, OFFER_AUDIO);
+ a1_->SetLocal(TestObserver::OFFER, a1_->offer());
+ ASSERT_EQ(a1_->pObserver->lastStatusCode,
+ PeerConnectionImpl::kNoError);
+ a1_->RemoveLastStreamAdded();
+ ASSERT_EQ(a1_->pObserver->lastStatusCode,
+ PeerConnectionImpl::kInvalidState);
+}
+
+TEST_P(SignalingTest, AddCandidateInHaveLocalOffer) {
+ OfferOptions options;
+ CreateOffer(options, OFFER_AUDIO);
+ a1_->SetLocal(TestObserver::OFFER, a1_->offer());
+ ASSERT_EQ(a1_->pObserver->lastAddIceStatusCode,
+ PeerConnectionImpl::kNoError);
+ a1_->AddIceCandidate(strSampleCandidate,
+ strSampleMid, nSamplelevel, false);
+ ASSERT_EQ(PeerConnectionImpl::kInvalidState,
+ a1_->pObserver->lastAddIceStatusCode);
+}
+
+TEST_F(SignalingAgentTest, CreateOffer) {
+ CreateAgent(TestStunServer::GetInstance()->addr(),
+ TestStunServer::GetInstance()->port());
+ OfferOptions options;
+ agent(0)->CreateOffer(options, OFFER_AUDIO);
+}
+
+TEST_F(SignalingAgentTest, SetLocalWithoutCreateOffer) {
+ CreateAgent(TestStunServer::GetInstance()->addr(),
+ TestStunServer::GetInstance()->port());
+ CreateAgent(TestStunServer::GetInstance()->addr(),
+ TestStunServer::GetInstance()->port());
+ OfferOptions options;
+ agent(0)->CreateOffer(options, OFFER_AUDIO);
+ agent(1)->SetLocal(TestObserver::OFFER,
+ agent(0)->offer(),
+ true,
+ PCImplSignalingState::SignalingStable);
+}
+
+TEST_F(SignalingAgentTest, SetLocalWithoutCreateAnswer) {
+ CreateAgent(TestStunServer::GetInstance()->addr(),
+ TestStunServer::GetInstance()->port());
+ CreateAgent(TestStunServer::GetInstance()->addr(),
+ TestStunServer::GetInstance()->port());
+ CreateAgent(TestStunServer::GetInstance()->addr(),
+ TestStunServer::GetInstance()->port());
+ OfferOptions options;
+ agent(0)->CreateOffer(options, OFFER_AUDIO);
+ agent(1)->SetRemote(TestObserver::OFFER, agent(0)->offer());
+ agent(1)->CreateAnswer(ANSWER_AUDIO);
+ agent(2)->SetRemote(TestObserver::OFFER, agent(0)->offer());
+ // Use agent 1's answer on agent 2, should fail
+ agent(2)->SetLocal(TestObserver::ANSWER,
+ agent(1)->answer(),
+ true,
+ PCImplSignalingState::SignalingHaveRemoteOffer);
+}
+
+TEST_F(SignalingAgentTest, CreateOfferSetLocalTrickleTestServer) {
+ TestStunServer::GetInstance()->SetActive(false);
+ TestStunServer::GetInstance()->SetResponseAddr(
+ kBogusSrflxAddress, kBogusSrflxPort);
+
+ CreateAgent(
+ TestStunServer::GetInstance()->addr(),
+ TestStunServer::GetInstance()->port());
+
+ OfferOptions options;
+ agent(0)->CreateOffer(options, OFFER_AUDIO);
+
+ // Verify that the bogus addr is not there.
+ ASSERT_FALSE(agent(0)->OfferContains(kBogusSrflxAddress));
+
+ // Now enable the STUN server.
+ TestStunServer::GetInstance()->SetActive(true);
+
+ agent(0)->SetLocal(TestObserver::OFFER, agent(0)->offer());
+ agent(0)->WaitForGather();
+
+ // Verify that we got our candidates.
+ ASSERT_LE(2U, agent(0)->MatchingCandidates(kBogusSrflxAddress));
+
+ // Verify that the candidates appear in the offer.
+ size_t match;
+ match = agent(0)->getLocalDescription().find(kBogusSrflxAddress);
+ ASSERT_LT(0U, match);
+}
+
+
+TEST_F(SignalingAgentTest, CreateAnswerSetLocalTrickleTestServer) {
+ TestStunServer::GetInstance()->SetActive(false);
+ TestStunServer::GetInstance()->SetResponseAddr(
+ kBogusSrflxAddress, kBogusSrflxPort);
+
+ CreateAgent(
+ TestStunServer::GetInstance()->addr(),
+ TestStunServer::GetInstance()->port());
+
+ std::string offer(strG711SdpOffer);
+ agent(0)->SetRemote(TestObserver::OFFER, offer, true,
+ PCImplSignalingState::SignalingHaveRemoteOffer);
+ ASSERT_EQ(agent(0)->pObserver->lastStatusCode,
+ PeerConnectionImpl::kNoError);
+
+ agent(0)->CreateAnswer(ANSWER_AUDIO);
+
+ // Verify that the bogus addr is not there.
+ ASSERT_FALSE(agent(0)->AnswerContains(kBogusSrflxAddress));
+
+ // Now enable the STUN server.
+ TestStunServer::GetInstance()->SetActive(true);
+
+ agent(0)->SetLocal(TestObserver::ANSWER, agent(0)->answer());
+ agent(0)->WaitForGather();
+
+ // Verify that we got our candidates.
+ ASSERT_LE(2U, agent(0)->MatchingCandidates(kBogusSrflxAddress));
+
+ // Verify that the candidates appear in the answer.
+ size_t match;
+ match = agent(0)->getLocalDescription().find(kBogusSrflxAddress);
+ ASSERT_LT(0U, match);
+}
+
+
+
+TEST_F(SignalingAgentTest, CreateLotsAndWait) {
+ int i;
+
+ for (i=0; i < 100; i++) {
+ if (!CreateAgent())
+ break;
+ std::cerr << "Created agent " << i << std::endl;
+ }
+ PR_Sleep(1000); // Wait to see if we crash
+}
+
+// Test for bug 856433.
+TEST_F(SignalingAgentTest, CreateNoInit) {
+ CreateAgentNoInit();
+}
+
+
+/*
+ * Test for Bug 843595
+ */
+TEST_P(SignalingTest, missingUfrag)
+{
+ EnsureInit();
+
+ OfferOptions options;
+ std::string offer =
+ "v=0\r\n"
+ "o=Mozilla-SIPUA 2208 0 IN IP4 0.0.0.0\r\n"
+ "s=SIP Call\r\n"
+ "t=0 0\r\n"
+ "a=ice-pwd:4450d5a4a5f097855c16fa079893be18\r\n"
+ "a=fingerprint:sha-256 23:9A:2E:43:94:42:CF:46:68:FC:62:F9:F4:48:61:DB:"
+ "2F:8C:C9:FF:6B:25:54:9D:41:09:EF:83:A8:19:FC:B6\r\n"
+ "m=audio 56187 UDP/TLS/RTP/SAVPF 109 0 8 101\r\n"
+ "c=IN IP4 77.9.79.167\r\n"
+ "a=rtpmap:109 opus/48000/2\r\n"
+ "a=ptime:20\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "a=rtpmap:8 PCMA/8000\r\n"
+ "a=rtpmap:101 telephone-event/8000\r\n"
+ "a=fmtp:101 0-15\r\n"
+ "a=sendrecv\r\n"
+ "a=candidate:0 1 UDP 2113601791 192.168.178.20 56187 typ host\r\n"
+ "a=candidate:1 1 UDP 1694236671 77.9.79.167 56187 typ srflx raddr "
+ "192.168.178.20 rport 56187\r\n"
+ "a=candidate:0 2 UDP 2113601790 192.168.178.20 52955 typ host\r\n"
+ "a=candidate:1 2 UDP 1694236670 77.9.79.167 52955 typ srflx raddr "
+ "192.168.178.20 rport 52955\r\n"
+ "m=video 49929 UDP/TLS/RTP/SAVPF 120\r\n"
+ "c=IN IP4 77.9.79.167\r\n"
+ "a=rtpmap:120 VP8/90000\r\n"
+ "a=recvonly\r\n"
+ "a=candidate:0 1 UDP 2113601791 192.168.178.20 49929 typ host\r\n"
+ "a=candidate:1 1 UDP 1694236671 77.9.79.167 49929 typ srflx raddr "
+ "192.168.178.20 rport 49929\r\n"
+ "a=candidate:0 2 UDP 2113601790 192.168.178.20 50769 typ host\r\n"
+ "a=candidate:1 2 UDP 1694236670 77.9.79.167 50769 typ srflx raddr "
+ "192.168.178.20 rport 50769\r\n"
+ "m=application 54054 DTLS/SCTP 5000\r\n"
+ "c=IN IP4 77.9.79.167\r\n"
+ "a=fmtp:HuRUu]Dtcl\\zM,7(OmEU%O$gU]x/z\tD protocol=webrtc-datachannel;"
+ "streams=16\r\n"
+ "a=sendrecv\r\n";
+
+ // Need to create an offer, since that's currently required by our
+ // FSM. This may change in the future.
+ a1_->CreateOffer(options, OFFER_AV);
+ a1_->SetLocal(TestObserver::OFFER, a1_->offer(), true);
+ // We now detect the missing ICE parameters at SetRemoteDescription
+ a2_->SetRemote(TestObserver::OFFER, offer, true,
+ PCImplSignalingState::SignalingStable);
+ ASSERT_TRUE(a2_->pObserver->state == TestObserver::stateError);
+}
+
+TEST_P(SignalingTest, AudioOnlyCalleeNoRtcpMux)
+{
+ EnsureInit();
+
+ OfferOptions options;
+
+ a1_->CreateOffer(options, OFFER_AUDIO);
+ a1_->SetLocal(TestObserver::OFFER, a1_->offer(), false);
+ ParsedSDP sdpWrapper(a1_->offer());
+ sdpWrapper.DeleteLine("a=rtcp-mux");
+ std::cout << "Modified SDP " << std::endl
+ << indent(sdpWrapper.getSdp()) << std::endl;
+ a2_->SetRemote(TestObserver::OFFER, sdpWrapper.getSdp(), false);
+ a2_->CreateAnswer(OFFER_AUDIO | ANSWER_AUDIO);
+ a2_->SetLocal(TestObserver::ANSWER, a2_->answer(), false);
+ a1_->SetRemote(TestObserver::ANSWER, a2_->answer(), false);
+
+ a1_->mExpectRtcpMuxAudio = false;
+ a2_->mExpectRtcpMuxAudio = false;
+
+ // Answer should not have a=rtcp-mux
+ ASSERT_EQ(a2_->getLocalDescription().find("\r\na=rtcp-mux"),
+ std::string::npos) << "SDP was: " << a2_->getLocalDescription();
+
+ WaitForCompleted();
+
+ CheckPipelines();
+ CheckStreams();
+
+ CloseStreams();
+}
+
+
+
+TEST_P(SignalingTest, AudioOnlyG722Only)
+{
+ EnsureInit();
+
+ OfferOptions options;
+
+ a1_->CreateOffer(options, OFFER_AUDIO);
+ a1_->SetLocal(TestObserver::OFFER, a1_->offer(), false);
+ ParsedSDP sdpWrapper(a1_->offer());
+ sdpWrapper.ReplaceLine("m=audio",
+ "m=audio 65375 UDP/TLS/RTP/SAVPF 9\r\n");
+ std::cout << "Modified SDP " << std::endl
+ << indent(sdpWrapper.getSdp()) << std::endl;
+ a2_->SetRemote(TestObserver::OFFER, sdpWrapper.getSdp(), false);
+ a2_->CreateAnswer(OFFER_AUDIO | ANSWER_AUDIO);
+ a2_->SetLocal(TestObserver::ANSWER, a2_->answer(), false);
+ a1_->SetRemote(TestObserver::ANSWER, a2_->answer(), false);
+ ASSERT_NE(a2_->getLocalDescription().find("UDP/TLS/RTP/SAVPF 9\r"),
+ std::string::npos);
+ ASSERT_NE(a2_->getLocalDescription().find("a=rtpmap:9 G722/8000"), std::string::npos);
+
+ WaitForCompleted();
+
+ CheckPipelines();
+ CheckStreams();
+
+ CloseStreams();
+}
+
+TEST_P(SignalingTest, AudioOnlyG722MostPreferred)
+{
+ EnsureInit();
+
+ OfferOptions options;
+
+ a1_->CreateOffer(options, OFFER_AUDIO);
+ a1_->SetLocal(TestObserver::OFFER, a1_->offer(), false);
+ ParsedSDP sdpWrapper(a1_->offer());
+ sdpWrapper.ReplaceLine("m=audio",
+ "m=audio 65375 UDP/TLS/RTP/SAVPF 9 0 8 109\r\n");
+ std::cout << "Modified SDP " << std::endl
+ << indent(sdpWrapper.getSdp()) << std::endl;
+ a2_->SetRemote(TestObserver::OFFER, sdpWrapper.getSdp(), false);
+ a2_->CreateAnswer(OFFER_AUDIO | ANSWER_AUDIO);
+ a2_->SetLocal(TestObserver::ANSWER, a2_->answer(), false);
+ a1_->SetRemote(TestObserver::ANSWER, a2_->answer(), false);
+ ASSERT_NE(a2_->getLocalDescription().find("UDP/TLS/RTP/SAVPF 9"),
+ std::string::npos);
+ ASSERT_NE(a2_->getLocalDescription().find("a=rtpmap:9 G722/8000"), std::string::npos);
+
+ CheckPipelines();
+ CheckStreams();
+
+ CloseStreams();
+}
+
+TEST_P(SignalingTest, AudioOnlyG722Rejected)
+{
+ EnsureInit();
+
+ OfferOptions options;
+
+ a1_->CreateOffer(options, OFFER_AUDIO);
+ // creating different SDPs as a workaround for rejecting codecs
+ // this way the answerer should pick a codec with lower priority
+ a1_->SetLocal(TestObserver::OFFER, a1_->offer(), false);
+ ParsedSDP sdpWrapper(a1_->offer());
+ sdpWrapper.ReplaceLine("m=audio",
+ "m=audio 65375 UDP/TLS/RTP/SAVPF 0 8\r\n");
+ std::cout << "Modified SDP offer " << std::endl
+ << indent(sdpWrapper.getSdp()) << std::endl;
+ a2_->SetRemote(TestObserver::OFFER, sdpWrapper.getSdp(), false);
+ a2_->CreateAnswer(OFFER_AUDIO | ANSWER_AUDIO);
+ a2_->SetLocal(TestObserver::ANSWER, a2_->answer(), false);
+ a1_->SetRemote(TestObserver::ANSWER, a2_->answer(), false);
+ // TODO(bug 814227): Use commented out code instead.
+ ASSERT_NE(a2_->getLocalDescription().find("UDP/TLS/RTP/SAVPF 0\r"),
+ std::string::npos);
+ // ASSERT_NE(a2_->getLocalDescription().find("UDP/TLS/RTP/SAVPF 0 8\r"), std::string::npos);
+ ASSERT_NE(a2_->getLocalDescription().find("a=rtpmap:0 PCMU/8000"), std::string::npos);
+ ASSERT_EQ(a2_->getLocalDescription().find("a=rtpmap:109 opus/48000/2"), std::string::npos);
+ ASSERT_EQ(a2_->getLocalDescription().find("a=rtpmap:9 G722/8000"), std::string::npos);
+
+ CheckPipelines();
+ CheckStreams();
+
+ CloseStreams();
+}
+
+TEST_P(SignalingTest, RestartIce)
+{
+ OfferOptions options;
+ OfferAnswer(options, OFFER_AV | ANSWER_AV);
+
+ options.setBoolOption("IceRestart", true);
+ OfferAnswer(options, OFFER_NONE);
+
+ CloseStreams();
+}
+
+TEST_P(SignalingTest, FullCallAudioNoMuxVideoMux)
+{
+ if (UseBundle()) {
+ // This test doesn't make sense for bundle
+ return;
+ }
+
+ EnsureInit();
+
+ OfferOptions options;
+
+ a1_->CreateOffer(options, OFFER_AV);
+ a1_->SetLocal(TestObserver::OFFER, a1_->offer(), false);
+ ParsedSDP sdpWrapper(a1_->offer());
+ sdpWrapper.DeleteLine("a=rtcp-mux");
+ std::cout << "Modified SDP " << std::endl
+ << indent(sdpWrapper.getSdp()) << std::endl;
+ a2_->SetRemote(TestObserver::OFFER, sdpWrapper.getSdp(), false);
+ a2_->CreateAnswer(OFFER_AV | ANSWER_AV);
+ a2_->SetLocal(TestObserver::ANSWER, a2_->answer(), false);
+ a1_->SetRemote(TestObserver::ANSWER, a2_->answer(), false);
+
+ // Answer should have only one a=rtcp-mux line
+ size_t match = a2_->getLocalDescription().find("\r\na=rtcp-mux");
+ ASSERT_NE(match, std::string::npos);
+ match = a2_->getLocalDescription().find("\r\na=rtcp-mux", match + 1);
+ ASSERT_EQ(match, std::string::npos);
+
+ a1_->mExpectRtcpMuxAudio = false;
+ a2_->mExpectRtcpMuxAudio = false;
+
+ WaitForCompleted();
+ CheckPipelines();
+ CheckStreams();
+
+ CloseStreams();
+}
+
+// TODO: Move to jsep_sesion_unittest
+TEST_P(SignalingTest, RtcpFbInOffer)
+{
+ EnsureInit();
+ OfferOptions options;
+ a1_->CreateOffer(options, OFFER_AV);
+ const char *expected[] = { "nack", "nack pli", "ccm fir" };
+ CheckRtcpFbSdp(a1_->offer(), ARRAY_TO_SET(std::string, expected));
+}
+
+TEST_P(SignalingTest, RtcpFbOfferAll)
+{
+ const char *feedbackTypes[] = { "nack", "nack pli", "ccm fir" };
+ TestRtcpFbOffer(ARRAY_TO_SET(std::string, feedbackTypes),
+ true,
+ VideoSessionConduit::FrameRequestPli);
+}
+
+TEST_P(SignalingTest, RtcpFbOfferNoNackBasic)
+{
+ const char *feedbackTypes[] = { "nack pli", "ccm fir" };
+ TestRtcpFbOffer(ARRAY_TO_SET(std::string, feedbackTypes),
+ false,
+ VideoSessionConduit::FrameRequestPli);
+}
+
+TEST_P(SignalingTest, RtcpFbOfferNoNackPli)
+{
+ const char *feedbackTypes[] = { "nack", "ccm fir" };
+ TestRtcpFbOffer(ARRAY_TO_SET(std::string, feedbackTypes),
+ true,
+ VideoSessionConduit::FrameRequestFir);
+}
+
+TEST_P(SignalingTest, RtcpFbOfferNoCcmFir)
+{
+ const char *feedbackTypes[] = { "nack", "nack pli" };
+ TestRtcpFbOffer(ARRAY_TO_SET(std::string, feedbackTypes),
+ true,
+ VideoSessionConduit::FrameRequestPli);
+}
+
+TEST_P(SignalingTest, RtcpFbOfferNoNack)
+{
+ const char *feedbackTypes[] = { "ccm fir" };
+ TestRtcpFbOffer(ARRAY_TO_SET(std::string, feedbackTypes),
+ false,
+ VideoSessionConduit::FrameRequestFir);
+}
+
+TEST_P(SignalingTest, RtcpFbOfferNoFrameRequest)
+{
+ const char *feedbackTypes[] = { "nack" };
+ TestRtcpFbOffer(ARRAY_TO_SET(std::string, feedbackTypes),
+ true,
+ VideoSessionConduit::FrameRequestNone);
+}
+
+TEST_P(SignalingTest, RtcpFbOfferPliOnly)
+{
+ const char *feedbackTypes[] = { "nack pli" };
+ TestRtcpFbOffer(ARRAY_TO_SET(std::string, feedbackTypes),
+ false,
+ VideoSessionConduit::FrameRequestPli);
+}
+
+TEST_P(SignalingTest, RtcpFbOfferNoFeedback)
+{
+ const char *feedbackTypes[] = { };
+ TestRtcpFbOffer(ARRAY_TO_SET(std::string, feedbackTypes),
+ false,
+ VideoSessionConduit::FrameRequestNone);
+}
+
+TEST_P(SignalingTest, RtcpFbAnswerAll)
+{
+ const char *feedbackTypes[] = { "nack", "nack pli", "ccm fir" };
+ TestRtcpFbAnswer(ARRAY_TO_SET(std::string, feedbackTypes),
+ true,
+ VideoSessionConduit::FrameRequestPli);
+}
+
+TEST_P(SignalingTest, RtcpFbAnswerNoNackBasic)
+{
+ const char *feedbackTypes[] = { "nack pli", "ccm fir" };
+ TestRtcpFbAnswer(ARRAY_TO_SET(std::string, feedbackTypes),
+ false,
+ VideoSessionConduit::FrameRequestPli);
+}
+
+TEST_P(SignalingTest, RtcpFbAnswerNoNackPli)
+{
+ const char *feedbackTypes[] = { "nack", "ccm fir" };
+ TestRtcpFbAnswer(ARRAY_TO_SET(std::string, feedbackTypes),
+ true,
+ VideoSessionConduit::FrameRequestFir);
+}
+
+TEST_P(SignalingTest, RtcpFbAnswerNoCcmFir)
+{
+ const char *feedbackTypes[] = { "nack", "nack pli" };
+ TestRtcpFbAnswer(ARRAY_TO_SET(std::string, feedbackTypes),
+ true,
+ VideoSessionConduit::FrameRequestPli);
+}
+
+TEST_P(SignalingTest, RtcpFbAnswerNoNack)
+{
+ const char *feedbackTypes[] = { "ccm fir" };
+ TestRtcpFbAnswer(ARRAY_TO_SET(std::string, feedbackTypes),
+ false,
+ VideoSessionConduit::FrameRequestFir);
+}
+
+TEST_P(SignalingTest, RtcpFbAnswerNoFrameRequest)
+{
+ const char *feedbackTypes[] = { "nack" };
+ TestRtcpFbAnswer(ARRAY_TO_SET(std::string, feedbackTypes),
+ true,
+ VideoSessionConduit::FrameRequestNone);
+}
+
+TEST_P(SignalingTest, RtcpFbAnswerPliOnly)
+{
+ const char *feedbackTypes[] = { "nack pli" };
+ TestRtcpFbAnswer(ARRAY_TO_SET(std::string, feedbackTypes),
+ 0,
+ VideoSessionConduit::FrameRequestPli);
+}
+
+TEST_P(SignalingTest, RtcpFbAnswerNoFeedback)
+{
+ const char *feedbackTypes[] = { };
+ TestRtcpFbAnswer(ARRAY_TO_SET(std::string, feedbackTypes),
+ 0,
+ VideoSessionConduit::FrameRequestNone);
+}
+
+// In this test we will change the offer SDP's a=setup value
+// from actpass to passive. This will make the answer do active.
+TEST_P(SignalingTest, AudioCallForceDtlsRoles)
+{
+ EnsureInit();
+
+ OfferOptions options;
+ size_t match;
+
+ a1_->CreateOffer(options, OFFER_AUDIO);
+
+ // By default the offer should give actpass
+ std::string offer(a1_->offer());
+ match = offer.find("\r\na=setup:actpass");
+ ASSERT_NE(match, std::string::npos);
+ // Now replace the actpass with passive so that the answer will
+ // return active
+ offer.replace(match, strlen("\r\na=setup:actpass"),
+ "\r\na=setup:passive");
+ std::cout << "Modified SDP " << std::endl
+ << indent(offer) << std::endl;
+
+ a1_->SetLocal(TestObserver::OFFER, offer, false);
+ a2_->SetRemote(TestObserver::OFFER, offer, false);
+ a2_->CreateAnswer(OFFER_AUDIO | ANSWER_AUDIO);
+
+ // Now the answer should contain a=setup:active
+ std::string answer(a2_->answer());
+ match = answer.find("\r\na=setup:active");
+ ASSERT_NE(match, std::string::npos);
+
+ // This should setup the DTLS with the same roles
+ // as the regular tests above.
+ a2_->SetLocal(TestObserver::ANSWER, a2_->answer(), false);
+ a1_->SetRemote(TestObserver::ANSWER, a2_->answer(), false);
+
+ WaitForCompleted();
+
+ CheckPipelines();
+ CheckStreams();
+
+ CloseStreams();
+}
+
+// In this test we will change the offer SDP's a=setup value
+// from actpass to active. This will make the answer do passive
+TEST_P(SignalingTest, AudioCallReverseDtlsRoles)
+{
+ EnsureInit();
+
+ OfferOptions options;
+ size_t match;
+
+ a1_->CreateOffer(options, OFFER_AUDIO);
+
+ // By default the offer should give actpass
+ std::string offer(a1_->offer());
+ match = offer.find("\r\na=setup:actpass");
+ ASSERT_NE(match, std::string::npos);
+ // Now replace the actpass with active so that the answer will
+ // return passive
+ offer.replace(match, strlen("\r\na=setup:actpass"),
+ "\r\na=setup:active");
+ std::cout << "Modified SDP " << std::endl
+ << indent(offer) << std::endl;
+
+ a1_->SetLocal(TestObserver::OFFER, offer, false);
+ a2_->SetRemote(TestObserver::OFFER, offer, false);
+ a2_->CreateAnswer(OFFER_AUDIO | ANSWER_AUDIO);
+
+ // Now the answer should contain a=setup:passive
+ std::string answer(a2_->answer());
+ match = answer.find("\r\na=setup:passive");
+ ASSERT_NE(match, std::string::npos);
+
+ // This should setup the DTLS with the opposite roles
+ // than the regular tests above.
+ a2_->SetLocal(TestObserver::ANSWER, a2_->answer(), false);
+ a1_->SetRemote(TestObserver::ANSWER, a2_->answer(), false);
+
+ WaitForCompleted();
+
+ CheckPipelines();
+ CheckStreams();
+
+ CloseStreams();
+}
+
+// In this test we will change the answer SDP's a=setup value
+// from active to passive. This will make both sides do
+// active and should not connect.
+TEST_P(SignalingTest, AudioCallMismatchDtlsRoles)
+{
+ EnsureInit();
+
+ OfferOptions options;
+ size_t match;
+
+ a1_->CreateOffer(options, OFFER_AUDIO);
+
+ // By default the offer should give actpass
+ std::string offer(a1_->offer());
+ match = offer.find("\r\na=setup:actpass");
+ ASSERT_NE(match, std::string::npos);
+ a1_->SetLocal(TestObserver::OFFER, offer, false);
+ a2_->SetRemote(TestObserver::OFFER, offer, false);
+ a2_->CreateAnswer(OFFER_AUDIO | ANSWER_AUDIO);
+
+ // Now the answer should contain a=setup:active
+ std::string answer(a2_->answer());
+ match = answer.find("\r\na=setup:active");
+ ASSERT_NE(match, std::string::npos);
+ a2_->SetLocal(TestObserver::ANSWER, answer, false);
+
+ // Now replace the active with passive so that the offerer will
+ // also do active.
+ answer.replace(match, strlen("\r\na=setup:active"),
+ "\r\na=setup:passive");
+ std::cout << "Modified SDP " << std::endl
+ << indent(answer) << std::endl;
+
+ // This should setup the DTLS with both sides playing active
+ a1_->SetRemote(TestObserver::ANSWER, answer, false);
+
+ WaitForCompleted();
+
+ // Not using ASSERT_TRUE_WAIT here because we expect failure
+ PR_Sleep(500); // Wait for some data to get written
+
+ CloseStreams();
+
+ ASSERT_GE(a1_->GetPacketsSent(0), 4);
+ // In this case we should receive nothing.
+ ASSERT_EQ(a2_->GetPacketsReceived(0), 0);
+}
+
+// In this test we will change the offer SDP's a=setup value
+// from actpass to garbage. It should ignore the garbage value
+// and respond with setup:active
+TEST_P(SignalingTest, AudioCallGarbageSetup)
+{
+ EnsureInit();
+
+ OfferOptions options;
+ size_t match;
+
+ a1_->CreateOffer(options, OFFER_AUDIO);
+
+ // By default the offer should give actpass
+ std::string offer(a1_->offer());
+ match = offer.find("\r\na=setup:actpass");
+ ASSERT_NE(match, std::string::npos);
+ // Now replace the actpass with a garbage value
+ offer.replace(match, strlen("\r\na=setup:actpass"),
+ "\r\na=setup:G4rb4g3V4lu3");
+ std::cout << "Modified SDP " << std::endl
+ << indent(offer) << std::endl;
+
+ a1_->SetLocal(TestObserver::OFFER, offer, false);
+ a2_->SetRemote(TestObserver::OFFER, offer, false);
+ a2_->CreateAnswer(OFFER_AUDIO | ANSWER_AUDIO);
+
+ // Now the answer should contain a=setup:active
+ std::string answer(a2_->answer());
+ match = answer.find("\r\na=setup:active");
+ ASSERT_NE(match, std::string::npos);
+
+ // This should setup the DTLS with the same roles
+ // as the regular tests above.
+ a2_->SetLocal(TestObserver::ANSWER, a2_->answer(), false);
+ a1_->SetRemote(TestObserver::ANSWER, a2_->answer(), false);
+
+ WaitForCompleted();
+
+ CheckPipelines();
+ CheckStreams();
+
+ CloseStreams();
+}
+
+// In this test we will change the offer SDP to remove the
+// a=setup line. Answer should respond with a=setup:active.
+TEST_P(SignalingTest, AudioCallOfferNoSetupOrConnection)
+{
+ EnsureInit();
+
+ OfferOptions options;
+ size_t match;
+
+ a1_->CreateOffer(options, OFFER_AUDIO);
+
+ std::string offer(a1_->offer());
+ a1_->SetLocal(TestObserver::OFFER, offer, false);
+
+ // By default the offer should give setup:actpass
+ match = offer.find("\r\na=setup:actpass");
+ ASSERT_NE(match, std::string::npos);
+ // Remove the a=setup line
+ offer.replace(match, strlen("\r\na=setup:actpass"), "");
+ std::cout << "Modified SDP " << std::endl
+ << indent(offer) << std::endl;
+
+ a2_->SetRemote(TestObserver::OFFER, offer, false);
+ a2_->CreateAnswer(OFFER_AUDIO | ANSWER_AUDIO);
+
+ // Now the answer should contain a=setup:active
+ std::string answer(a2_->answer());
+ match = answer.find("\r\na=setup:active");
+ ASSERT_NE(match, std::string::npos);
+
+ // This should setup the DTLS with the same roles
+ // as the regular tests above.
+ a2_->SetLocal(TestObserver::ANSWER, a2_->answer(), false);
+ a1_->SetRemote(TestObserver::ANSWER, a2_->answer(), false);
+
+ WaitForCompleted();
+
+ CheckPipelines();
+ CheckStreams();
+
+ CloseStreams();
+}
+
+// In this test we will change the answer SDP to remove the
+// a=setup line. ICE should still connect since active will
+// be assumed.
+TEST_P(SignalingTest, AudioCallAnswerNoSetupOrConnection)
+{
+ EnsureInit();
+
+ OfferOptions options;
+ size_t match;
+
+ a1_->CreateOffer(options, OFFER_AUDIO);
+
+ // By default the offer should give setup:actpass
+ std::string offer(a1_->offer());
+ match = offer.find("\r\na=setup:actpass");
+ ASSERT_NE(match, std::string::npos);
+
+ a1_->SetLocal(TestObserver::OFFER, offer, false);
+ a2_->SetRemote(TestObserver::OFFER, offer, false);
+ a2_->CreateAnswer(OFFER_AUDIO | ANSWER_AUDIO);
+
+ // Now the answer should contain a=setup:active
+ std::string answer(a2_->answer());
+ match = answer.find("\r\na=setup:active");
+ ASSERT_NE(match, std::string::npos);
+ // Remove the a=setup line
+ answer.replace(match, strlen("\r\na=setup:active"), "");
+ std::cout << "Modified SDP " << std::endl
+ << indent(answer) << std::endl;
+
+ // This should setup the DTLS with the same roles
+ // as the regular tests above.
+ a2_->SetLocal(TestObserver::ANSWER, answer, false);
+ a1_->SetRemote(TestObserver::ANSWER, answer, false);
+
+ WaitForCompleted();
+
+ CheckPipelines();
+ CheckStreams();
+
+ CloseStreams();
+}
+
+
+TEST_P(SignalingTest, FullCallRealTrickle)
+{
+ OfferOptions options;
+ OfferAnswer(options, OFFER_AV | ANSWER_AV);
+ CloseStreams();
+}
+
+TEST_P(SignalingTest, FullCallRealTrickleTestServer)
+{
+ SetTestStunServer();
+
+ OfferOptions options;
+ OfferAnswer(options, OFFER_AV | ANSWER_AV);
+
+ TestStunServer::GetInstance()->SetActive(true);
+
+ CloseStreams();
+}
+
+TEST_P(SignalingTest, hugeSdp)
+{
+ EnsureInit();
+
+ OfferOptions options;
+ std::string offer =
+ "v=0\r\n"
+ "o=- 1109973417102828257 2 IN IP4 127.0.0.1\r\n"
+ "s=-\r\n"
+ "t=0 0\r\n"
+ "a=group:BUNDLE audio video\r\n"
+ "a=msid-semantic: WMS 1PBxet5BYh0oYodwsvNM4k6KiO2eWCX40VIP\r\n"
+ "m=audio 32952 UDP/TLS/RTP/SAVPF 111 103 104 0 8 107 106 105 13 126\r\n"
+ "c=IN IP4 128.64.32.16\r\n"
+ "a=rtcp:32952 IN IP4 128.64.32.16\r\n"
+ "a=candidate:77142221 1 udp 2113937151 192.168.137.1 54081 typ host generation 0\r\n"
+ "a=candidate:77142221 2 udp 2113937151 192.168.137.1 54081 typ host generation 0\r\n"
+ "a=candidate:983072742 1 udp 2113937151 172.22.0.56 54082 typ host generation 0\r\n"
+ "a=candidate:983072742 2 udp 2113937151 172.22.0.56 54082 typ host generation 0\r\n"
+ "a=candidate:2245074553 1 udp 1845501695 32.64.128.1 62397 typ srflx raddr 192.168.137.1 rport 54081 generation 0\r\n"
+ "a=candidate:2245074553 2 udp 1845501695 32.64.128.1 62397 typ srflx raddr 192.168.137.1 rport 54081 generation 0\r\n"
+ "a=candidate:2479353907 1 udp 1845501695 32.64.128.1 54082 typ srflx raddr 172.22.0.56 rport 54082 generation 0\r\n"
+ "a=candidate:2479353907 2 udp 1845501695 32.64.128.1 54082 typ srflx raddr 172.22.0.56 rport 54082 generation 0\r\n"
+ "a=candidate:1243276349 1 tcp 1509957375 192.168.137.1 0 typ host generation 0\r\n"
+ "a=candidate:1243276349 2 tcp 1509957375 192.168.137.1 0 typ host generation 0\r\n"
+ "a=candidate:1947960086 1 tcp 1509957375 172.22.0.56 0 typ host generation 0\r\n"
+ "a=candidate:1947960086 2 tcp 1509957375 172.22.0.56 0 typ host generation 0\r\n"
+ "a=candidate:1808221584 1 udp 33562367 128.64.32.16 32952 typ relay raddr 32.64.128.1 rport 62398 generation 0\r\n"
+ "a=candidate:1808221584 2 udp 33562367 128.64.32.16 32952 typ relay raddr 32.64.128.1 rport 62398 generation 0\r\n"
+ "a=candidate:507872740 1 udp 33562367 128.64.32.16 40975 typ relay raddr 32.64.128.1 rport 54085 generation 0\r\n"
+ "a=candidate:507872740 2 udp 33562367 128.64.32.16 40975 typ relay raddr 32.64.128.1 rport 54085 generation 0\r\n"
+ "a=ice-ufrag:xQuJwjX3V3eMA81k\r\n"
+ "a=ice-pwd:ZUiRmjS2GDhG140p73dAsSVP\r\n"
+ "a=ice-options:google-ice\r\n"
+ "a=fingerprint:sha-256 59:4A:8B:73:A7:73:53:71:88:D7:4D:58:28:0C:79:72:31:29:9B:05:37:DD:58:43:C2:D4:85:A2:B3:66:38:7A\r\n"
+ "a=setup:active\r\n"
+ "a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n"
+ "a=sendrecv\r\n"
+ "a=mid:audio\r\n"
+ "a=rtcp-mux\r\n"
+ "a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:/U44g3ULdtapeiSg+T3n6dDLBKIjpOhb/NXAL/2b\r\n"
+ "a=rtpmap:111 opus/48000/2\r\n"
+ "a=fmtp:111 minptime=10\r\n"
+ "a=rtpmap:103 ISAC/16000\r\n"
+ "a=rtpmap:104 ISAC/32000\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "a=rtpmap:8 PCMA/8000\r\n"
+ "a=rtpmap:107 CN/48000\r\n"
+ "a=rtpmap:106 CN/32000\r\n"
+ "a=rtpmap:105 CN/16000\r\n"
+ "a=rtpmap:13 CN/8000\r\n"
+ "a=rtpmap:126 telephone-event/8000\r\n"
+ "a=maxptime:60\r\n"
+ "a=ssrc:2271517329 cname:mKDNt7SQf6pwDlIn\r\n"
+ "a=ssrc:2271517329 msid:1PBxet5BYh0oYodwsvNM4k6KiO2eWCX40VIP 1PBxet5BYh0oYodwsvNM4k6KiO2eWCX40VIPa0\r\n"
+ "a=ssrc:2271517329 mslabel:1PBxet5BYh0oYodwsvNM4k6KiO2eWCX40VIP\r\n"
+ "a=ssrc:2271517329 label:1PBxet5BYh0oYodwsvNM4k6KiO2eWCX40VIPa0\r\n"
+ "m=video 32952 UDP/TLS/RTP/SAVPF 100 116 117\r\n"
+ "c=IN IP4 128.64.32.16\r\n"
+ "a=rtcp:32952 IN IP4 128.64.32.16\r\n"
+ "a=candidate:77142221 1 udp 2113937151 192.168.137.1 54081 typ host generation 0\r\n"
+ "a=candidate:77142221 2 udp 2113937151 192.168.137.1 54081 typ host generation 0\r\n"
+ "a=candidate:983072742 1 udp 2113937151 172.22.0.56 54082 typ host generation 0\r\n"
+ "a=candidate:983072742 2 udp 2113937151 172.22.0.56 54082 typ host generation 0\r\n"
+ "a=candidate:2245074553 1 udp 1845501695 32.64.128.1 62397 typ srflx raddr 192.168.137.1 rport 54081 generation 0\r\n"
+ "a=candidate:2245074553 2 udp 1845501695 32.64.128.1 62397 typ srflx raddr 192.168.137.1 rport 54081 generation 0\r\n"
+ "a=candidate:2479353907 1 udp 1845501695 32.64.128.1 54082 typ srflx raddr 172.22.0.56 rport 54082 generation 0\r\n"
+ "a=candidate:2479353907 2 udp 1845501695 32.64.128.1 54082 typ srflx raddr 172.22.0.56 rport 54082 generation 0\r\n"
+ "a=candidate:1243276349 1 tcp 1509957375 192.168.137.1 0 typ host generation 0\r\n"
+ "a=candidate:1243276349 2 tcp 1509957375 192.168.137.1 0 typ host generation 0\r\n"
+ "a=candidate:1947960086 1 tcp 1509957375 172.22.0.56 0 typ host generation 0\r\n"
+ "a=candidate:1947960086 2 tcp 1509957375 172.22.0.56 0 typ host generation 0\r\n"
+ "a=candidate:1808221584 1 udp 33562367 128.64.32.16 32952 typ relay raddr 32.64.128.1 rport 62398 generation 0\r\n"
+ "a=candidate:1808221584 2 udp 33562367 128.64.32.16 32952 typ relay raddr 32.64.128.1 rport 62398 generation 0\r\n"
+ "a=candidate:507872740 1 udp 33562367 128.64.32.16 40975 typ relay raddr 32.64.128.1 rport 54085 generation 0\r\n"
+ "a=candidate:507872740 2 udp 33562367 128.64.32.16 40975 typ relay raddr 32.64.128.1 rport 54085 generation 0\r\n"
+ "a=ice-ufrag:xQuJwjX3V3eMA81k\r\n"
+ "a=ice-pwd:ZUiRmjS2GDhG140p73dAsSVP\r\n"
+ "a=ice-options:google-ice\r\n"
+ "a=fingerprint:sha-256 59:4A:8B:73:A7:73:53:71:88:D7:4D:58:28:0C:79:72:31:29:9B:05:37:DD:58:43:C2:D4:85:A2:B3:66:38:7A\r\n"
+ "a=setup:active\r\n"
+ "a=extmap:2 urn:ietf:params:rtp-hdrext:toffset\r\n"
+ "a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\n"
+ "a=sendrecv\r\n"
+ "a=mid:video\r\n"
+ "a=rtcp-mux\r\n"
+ "a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:/U44g3ULdtapeiSg+T3n6dDLBKIjpOhb/NXAL/2b\r\n"
+ "a=rtpmap:100 VP8/90000\r\n"
+ "a=rtcp-fb:100 ccm fir\r\n"
+ "a=rtcp-fb:100 nack\r\n"
+ "a=rtcp-fb:100 goog-remb\r\n"
+ "a=rtpmap:116 red/90000\r\n"
+ "a=rtpmap:117 ulpfec/90000\r\n"
+ "a=ssrc:54724160 cname:mKDNt7SQf6pwDlIn\r\n"
+ "a=ssrc:54724160 msid:1PBxet5BYh0oYodwsvNM4k6KiO2eWCX40VIP 1PBxet5BYh0oYodwsvNM4k6KiO2eWCX40VIPv0\r\n"
+ "a=ssrc:54724160 mslabel:1PBxet5BYh0oYodwsvNM4k6KiO2eWCX40VIP\r\n"
+ "a=ssrc:54724160 label:1PBxet5BYh0oYodwsvNM4k6KiO2eWCX40VIPv0\r\n";
+
+ a1_->CreateOffer(options, OFFER_AV);
+ a1_->SetLocal(TestObserver::OFFER, a1_->offer(), true);
+
+ a2_->SetRemote(TestObserver::OFFER, offer, true);
+ ASSERT_GE(a2_->getRemoteDescription().length(), 4096U);
+ a2_->CreateAnswer(OFFER_AV);
+}
+
+// Test max_fs and max_fr prefs have proper impact on SDP offer
+TEST_P(SignalingTest, MaxFsFrInOffer)
+{
+ EnsureInit();
+
+ OfferOptions options;
+
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ ASSERT_TRUE(prefs);
+ FsFrPrefClearer prefClearer(prefs);
+
+ SetMaxFsFr(prefs, 300, 30);
+
+ a1_->CreateOffer(options, OFFER_AV);
+
+ // Verify that SDP contains correct max-fs and max-fr
+ CheckMaxFsFrSdp(a1_->offer(), 120, 300, 30);
+}
+
+// Test max_fs and max_fr prefs have proper impact on SDP answer
+TEST_P(SignalingTest, MaxFsFrInAnswer)
+{
+ EnsureInit();
+
+ OfferOptions options;
+
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ ASSERT_TRUE(prefs);
+ FsFrPrefClearer prefClearer(prefs);
+
+ a1_->CreateOffer(options, OFFER_AV);
+
+ SetMaxFsFr(prefs, 600, 60);
+
+ a2_->SetRemote(TestObserver::OFFER, a1_->offer());
+
+ a2_->CreateAnswer(OFFER_AV | ANSWER_AV);
+
+ // Verify that SDP contains correct max-fs and max-fr
+ CheckMaxFsFrSdp(a2_->answer(), 120, 600, 60);
+}
+
+// Test SDP offer has proper impact on callee's codec configuration
+TEST_P(SignalingTest, MaxFsFrCalleeCodec)
+{
+ EnsureInit();
+
+ OfferOptions options;
+
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ ASSERT_TRUE(prefs);
+ FsFrPrefClearer prefClearer(prefs);
+
+ SetMaxFsFr(prefs, 300, 30);
+ a1_->CreateOffer(options, OFFER_AV);
+
+ CheckMaxFsFrSdp(a1_->offer(), 120, 300, 30);
+
+ a1_->SetLocal(TestObserver::OFFER, a1_->offer());
+
+ SetMaxFsFr(prefs, 3601, 31);
+ a2_->SetRemote(TestObserver::OFFER, a1_->offer());
+
+ a2_->CreateAnswer(OFFER_AV | ANSWER_AV);
+
+ CheckMaxFsFrSdp(a2_->answer(), 120, 3601, 31);
+
+ a2_->SetLocal(TestObserver::ANSWER, a2_->answer());
+ a1_->SetRemote(TestObserver::ANSWER, a2_->answer());
+
+ WaitForCompleted();
+
+ CheckPipelines();
+ CheckStreams();
+
+ // Checking callee's video sending configuration does respect max-fs and
+ // max-fr in SDP offer.
+ RefPtr<mozilla::MediaPipeline> pipeline =
+ a2_->GetMediaPipeline(1, 0, 1);
+ ASSERT_TRUE(pipeline);
+ mozilla::MediaSessionConduit *conduit = pipeline->Conduit();
+ ASSERT_TRUE(conduit);
+ ASSERT_EQ(conduit->type(), mozilla::MediaSessionConduit::VIDEO);
+ mozilla::VideoSessionConduit *video_conduit =
+ static_cast<mozilla::VideoSessionConduit*>(conduit);
+
+ ASSERT_EQ(video_conduit->SendingMaxFs(), (unsigned short) 300);
+ ASSERT_EQ(video_conduit->SendingMaxFr(), (unsigned short) 30);
+}
+
+// Test SDP answer has proper impact on caller's codec configuration
+TEST_P(SignalingTest, MaxFsFrCallerCodec)
+{
+ EnsureInit();
+
+ OfferOptions options;
+
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ ASSERT_TRUE(prefs);
+ FsFrPrefClearer prefClearer(prefs);
+
+ a1_->CreateOffer(options, OFFER_AV);
+ a1_->SetLocal(TestObserver::OFFER, a1_->offer());
+
+ SetMaxFsFr(prefs, 600, 60);
+ a2_->SetRemote(TestObserver::OFFER, a1_->offer());
+
+ a2_->CreateAnswer(OFFER_AV | ANSWER_AV);
+
+ // Double confirm that SDP answer contains correct max-fs and max-fr
+ CheckMaxFsFrSdp(a2_->answer(), 120, 600, 60);
+
+ a2_->SetLocal(TestObserver::ANSWER, a2_->answer());
+ a1_->SetRemote(TestObserver::ANSWER, a2_->answer());
+
+ WaitForCompleted();
+
+ CheckPipelines();
+ CheckStreams();
+
+ // Checking caller's video sending configuration does respect max-fs and
+ // max-fr in SDP answer.
+ RefPtr<mozilla::MediaPipeline> pipeline =
+ a1_->GetMediaPipeline(1, 0, 1);
+ ASSERT_TRUE(pipeline);
+ mozilla::MediaSessionConduit *conduit = pipeline->Conduit();
+ ASSERT_TRUE(conduit);
+ ASSERT_EQ(conduit->type(), mozilla::MediaSessionConduit::VIDEO);
+ mozilla::VideoSessionConduit *video_conduit =
+ static_cast<mozilla::VideoSessionConduit*>(conduit);
+
+ ASSERT_EQ(video_conduit->SendingMaxFs(), (unsigned short) 600);
+ ASSERT_EQ(video_conduit->SendingMaxFr(), (unsigned short) 60);
+}
+
+// Validate offer with multiple video codecs
+TEST_P(SignalingTest, ValidateMultipleVideoCodecsInOffer)
+{
+ EnsureInit();
+ OfferOptions options;
+
+ a1_->CreateOffer(options, OFFER_AV);
+ std::string offer = a1_->offer();
+
+#ifdef H264_P0_SUPPORTED
+ ASSERT_NE(offer.find("UDP/TLS/RTP/SAVPF 120 126 97") ||
+ offer.find("UDP/TLS/RTP/SAVPF 120 121 126 97"), std::string::npos);
+#else
+ ASSERT_NE(offer.find("UDP/TLS/RTP/SAVPF 120 126") ||
+ offer.find("UDP/TLS/RTP/SAVPF 120 121 126"), std::string::npos);
+#endif
+ ASSERT_NE(offer.find("a=rtpmap:120 VP8/90000"), std::string::npos);
+ ASSERT_NE(offer.find("a=rtpmap:126 H264/90000"), std::string::npos);
+ ASSERT_NE(offer.find("a=fmtp:126 profile-level-id="), std::string::npos);
+ ASSERT_NE(offer.find("a=rtcp-fb:120 nack"), std::string::npos);
+ ASSERT_NE(offer.find("a=rtcp-fb:120 nack pli"), std::string::npos);
+ ASSERT_NE(offer.find("a=rtcp-fb:120 ccm fir"), std::string::npos);
+ ASSERT_NE(offer.find("a=rtcp-fb:126 nack"), std::string::npos);
+ ASSERT_NE(offer.find("a=rtcp-fb:126 nack pli"), std::string::npos);
+ ASSERT_NE(offer.find("a=rtcp-fb:126 ccm fir"), std::string::npos);
+#ifdef H264_P0_SUPPORTED
+ ASSERT_NE(offer.find("a=rtpmap:97 H264/90000"), std::string::npos);
+ ASSERT_NE(offer.find("a=fmtp:97 profile-level-id="), std::string::npos);
+ ASSERT_NE(offer.find("a=rtcp-fb:97 nack"), std::string::npos);
+ ASSERT_NE(offer.find("a=rtcp-fb:97 nack pli"), std::string::npos);
+ ASSERT_NE(offer.find("a=rtcp-fb:97 ccm fir"), std::string::npos);
+#endif
+}
+
+// Remove VP8 from offer and check that answer negotiates H264 P1 correctly and ignores unknown params
+TEST_P(SignalingTest, RemoveVP8FromOfferWithP1First)
+{
+ EnsureInit();
+
+ OfferOptions options;
+ size_t match;
+
+ a1_->CreateOffer(options, OFFER_AV);
+
+ // Remove VP8 from offer
+ std::string offer = a1_->offer();
+ match = offer.find("UDP/TLS/RTP/SAVPF 120");
+ if (match != std::string::npos) {
+ offer.replace(match, strlen("UDP/TLS/RTP/SAVPF 120"), "UDP/TLS/RTP/SAVPF");
+ }
+ match = offer.find("UDP/TLS/RTP/SAVPF 121");
+ if (match != std::string::npos) {
+ offer.replace(match, strlen("UDP/TLS/RTP/SAVPF 121"), "UDP/TLS/RTP/SAVPF");
+ }
+ match = offer.find("UDP/TLS/RTP/SAVPF 126");
+ ASSERT_NE(std::string::npos, match);
+
+ match = offer.find("profile-level-id");
+ ASSERT_NE(std::string::npos, match);
+ offer.replace(match,
+ strlen("profile-level-id"),
+ "max-foo=1234;profile-level-id");
+
+ ParsedSDP sdpWrapper(offer);
+ sdpWrapper.DeleteLines("a=rtcp-fb:120");
+ sdpWrapper.DeleteLine("a=rtpmap:120");
+
+ std::cout << "Modified SDP " << std::endl
+ << indent(sdpWrapper.getSdp()) << std::endl;
+
+ // P1 should be offered first
+ ASSERT_NE(offer.find("UDP/TLS/RTP/SAVPF 126"), std::string::npos);
+
+ a1_->SetLocal(TestObserver::OFFER, sdpWrapper.getSdp());
+ a2_->SetRemote(TestObserver::OFFER, sdpWrapper.getSdp(), false);
+ a2_->CreateAnswer(OFFER_AV|ANSWER_AV);
+
+ std::string answer(a2_->answer());
+
+ // Validate answer SDP
+ ASSERT_NE(answer.find("UDP/TLS/RTP/SAVPF 126"), std::string::npos);
+ ASSERT_NE(answer.find("a=rtpmap:126 H264/90000"), std::string::npos);
+ ASSERT_NE(answer.find("a=rtcp-fb:126 nack"), std::string::npos);
+ ASSERT_NE(answer.find("a=rtcp-fb:126 nack pli"), std::string::npos);
+ ASSERT_NE(answer.find("a=rtcp-fb:126 ccm fir"), std::string::npos);
+ // Ensure VP8 removed
+ ASSERT_EQ(answer.find("a=rtpmap:120 VP8/90000"), std::string::npos);
+ ASSERT_EQ(answer.find("a=rtcp-fb:120"), std::string::npos);
+}
+
+// Insert H.264 before VP8 in Offer, check answer selects H.264
+TEST_P(SignalingTest, OfferWithH264BeforeVP8)
+{
+ EnsureInit();
+
+ OfferOptions options;
+ size_t match;
+
+ a1_->CreateOffer(options, OFFER_AV);
+
+ // Swap VP8 and P1 in offer
+ std::string offer = a1_->offer();
+#ifdef H264_P0_SUPPORTED
+ match = offer.find("UDP/TLS/RTP/SAVPF 120 126 97");
+ ASSERT_NE(std::string::npos, match);
+ offer.replace(match,
+ strlen("UDP/TLS/RTP/SAVPF 126 120 97"),
+ "UDP/TLS/RTP/SAVPF 126 120 97");
+#else
+ match = offer.find("UDP/TLS/RTP/SAVPF 120 126");
+ ASSERT_NE(std::string::npos, match);
+ offer.replace(match,
+ strlen("UDP/TLS/RTP/SAVPF 126 120"),
+ "UDP/TLS/RTP/SAVPF 126 120");
+#endif
+
+ match = offer.find("a=rtpmap:126 H264/90000");
+ ASSERT_NE(std::string::npos, match);
+ offer.replace(match,
+ strlen("a=rtpmap:120 VP8/90000"),
+ "a=rtpmap:120 VP8/90000");
+
+ match = offer.find("a=rtpmap:120 VP8/90000");
+ ASSERT_NE(std::string::npos, match);
+ offer.replace(match,
+ strlen("a=rtpmap:126 H264/90000"),
+ "a=rtpmap:126 H264/90000");
+
+ std::cout << "Modified SDP " << std::endl
+ << indent(offer) << std::endl;
+
+ // P1 should be offered first
+#ifdef H264_P0_SUPPORTED
+ ASSERT_NE(offer.find("UDP/TLS/RTP/SAVPF 126 120 97"), std::string::npos);
+#else
+ ASSERT_NE(offer.find("UDP/TLS/RTP/SAVPF 126 120"), std::string::npos);
+#endif
+
+ a1_->SetLocal(TestObserver::OFFER, offer);
+ a2_->SetRemote(TestObserver::OFFER, offer, false);
+ a2_->CreateAnswer(OFFER_AV|ANSWER_AV);
+
+ std::string answer(a2_->answer());
+
+ // Validate answer SDP
+ ASSERT_NE(answer.find("UDP/TLS/RTP/SAVPF 126"), std::string::npos);
+ ASSERT_NE(answer.find("a=rtpmap:126 H264/90000"), std::string::npos);
+ ASSERT_NE(answer.find("a=rtcp-fb:126 nack"), std::string::npos);
+ ASSERT_NE(answer.find("a=rtcp-fb:126 nack pli"), std::string::npos);
+ ASSERT_NE(answer.find("a=rtcp-fb:126 ccm fir"), std::string::npos);
+}
+
+#ifdef H264_P0_SUPPORTED
+// Remove H.264 P1 and VP8 from offer, check answer negotiates H.264 P0
+TEST_P(SignalingTest, OfferWithOnlyH264P0)
+{
+ EnsureInit();
+
+ OfferOptions options;
+ size_t match;
+
+ a1_->CreateOffer(options, OFFER_AV);
+
+ // Remove VP8 from offer
+ std::string offer = a1_->offer();
+ match = offer.find("UDP/TLS/RTP/SAVPF 120 126");
+ ASSERT_NE(std::string::npos, match);
+ offer.replace(match,
+ strlen("UDP/TLS/RTP/SAVPF 120 126"),
+ "UDP/TLS/RTP/SAVPF");
+
+ ParsedSDP sdpWrapper(offer);
+ sdpWrapper.DeleteLines("a=rtcp-fb:120");
+ sdpWrapper.DeleteLine("a=rtpmap:120");
+ sdpWrapper.DeleteLines("a=rtcp-fb:126");
+ sdpWrapper.DeleteLine("a=rtpmap:126");
+ sdpWrapper.DeleteLine("a=fmtp:126");
+
+ std::cout << "Modified SDP " << std::endl
+ << indent(sdpWrapper.getSdp()) << std::endl;
+
+ // Offer shouldn't have P1 or VP8 now
+ offer = sdpWrapper.getSdp();
+ ASSERT_EQ(offer.find("a=rtpmap:126 H264/90000"), std::string::npos);
+ ASSERT_EQ(offer.find("a=rtpmap:120 VP8/90000"), std::string::npos);
+
+ // P0 should be offered first
+ ASSERT_NE(offer.find("UDP/TLS/RTP/SAVPF 97"), std::string::npos);
+
+ a1_->SetLocal(TestObserver::OFFER, offer);
+ a2_->SetRemote(TestObserver::OFFER, offer, false);
+ a2_->CreateAnswer(OFFER_AV|ANSWER_AV);
+
+ std::string answer(a2_->answer());
+
+ // validate answer SDP
+ ASSERT_NE(answer.find("UDP/TLS/RTP/SAVPF 97"), std::string::npos);
+ ASSERT_NE(answer.find("a=rtpmap:97 H264/90000"), std::string::npos);
+ ASSERT_NE(answer.find("a=rtcp-fb:97 nack"), std::string::npos);
+ ASSERT_NE(answer.find("a=rtcp-fb:97 nack pli"), std::string::npos);
+ ASSERT_NE(answer.find("a=rtcp-fb:97 ccm fir"), std::string::npos);
+ // Ensure VP8 and P1 removed
+ ASSERT_EQ(answer.find("a=rtpmap:126 H264/90000"), std::string::npos);
+ ASSERT_EQ(answer.find("a=rtpmap:120 VP8/90000"), std::string::npos);
+ ASSERT_EQ(answer.find("a=rtcp-fb:120"), std::string::npos);
+ ASSERT_EQ(answer.find("a=rtcp-fb:126"), std::string::npos);
+}
+#endif
+
+// Test negotiating an answer which has only H.264 P1
+// Which means replace VP8 with H.264 P1 in answer
+TEST_P(SignalingTest, AnswerWithoutVP8)
+{
+ EnsureInit();
+
+ OfferOptions options;
+
+ a1_->CreateOffer(options, OFFER_AV);
+ a1_->SetLocal(TestObserver::OFFER, a1_->offer());
+ a2_->SetRemote(TestObserver::OFFER, a1_->offer(), false);
+ a2_->CreateAnswer(OFFER_AV|ANSWER_AV);
+
+ std::string answer(a2_->answer());
+
+ // Ensure answer has VP8
+ ASSERT_NE(answer.find("\r\na=rtpmap:120 VP8/90000"), std::string::npos);
+
+ // Replace VP8 with H.264 P1
+ ParsedSDP sdpWrapper(a2_->answer());
+ sdpWrapper.AddLine("a=fmtp:126 profile-level-id=42e00c;level-asymmetry-allowed=1;packetization-mode=1\r\n");
+ size_t match;
+ answer = sdpWrapper.getSdp();
+
+ match = answer.find("UDP/TLS/RTP/SAVPF 120");
+ ASSERT_NE(std::string::npos, match);
+ answer.replace(match,
+ strlen("UDP/TLS/RTP/SAVPF 120"),
+ "UDP/TLS/RTP/SAVPF 126");
+
+ match = answer.find("\r\na=rtpmap:120 VP8/90000");
+ ASSERT_NE(std::string::npos, match);
+ answer.replace(match,
+ strlen("\r\na=rtpmap:126 H264/90000"),
+ "\r\na=rtpmap:126 H264/90000");
+
+ match = answer.find("\r\na=rtcp-fb:120 nack");
+ ASSERT_NE(std::string::npos, match);
+ answer.replace(match,
+ strlen("\r\na=rtcp-fb:126 nack"),
+ "\r\na=rtcp-fb:126 nack");
+
+ match = answer.find("\r\na=rtcp-fb:120 nack pli");
+ ASSERT_NE(std::string::npos, match);
+ answer.replace(match,
+ strlen("\r\na=rtcp-fb:126 nack pli"),
+ "\r\na=rtcp-fb:126 nack pli");
+
+ match = answer.find("\r\na=rtcp-fb:120 ccm fir");
+ ASSERT_NE(std::string::npos, match);
+ answer.replace(match,
+ strlen("\r\na=rtcp-fb:126 ccm fir"),
+ "\r\na=rtcp-fb:126 ccm fir");
+
+ std::cout << "Modified SDP " << std::endl << indent(answer) << std::endl;
+
+ a2_->SetLocal(TestObserver::ANSWER, answer, false);
+
+ ASSERT_EQ(a2_->pObserver->lastStatusCode,
+ PeerConnectionImpl::kNoError);
+
+ a1_->SetRemote(TestObserver::ANSWER, answer, false);
+
+ ASSERT_EQ(a1_->pObserver->lastStatusCode,
+ PeerConnectionImpl::kNoError);
+
+ WaitForCompleted();
+
+ // We cannot check pipelines/streams since the H264 stuff won't init.
+
+ CloseStreams();
+}
+
+// Test using a non preferred dynamic video payload type on answer negotiation
+TEST_P(SignalingTest, UseNonPrefferedPayloadTypeOnAnswer)
+{
+ EnsureInit();
+
+ OfferOptions options;
+ a1_->CreateOffer(options, OFFER_AV);
+ a1_->SetLocal(TestObserver::OFFER, a1_->offer());
+ a2_->SetRemote(TestObserver::OFFER, a1_->offer(), false);
+ a2_->CreateAnswer(OFFER_AV|ANSWER_AV);
+
+ std::string answer(a2_->answer());
+
+ // Ensure answer has VP8
+ ASSERT_NE(answer.find("\r\na=rtpmap:120 VP8/90000"), std::string::npos);
+
+ // Replace VP8 Payload Type with a non preferred value
+ size_t match;
+ match = answer.find("UDP/TLS/RTP/SAVPF 120");
+ ASSERT_NE(std::string::npos, match);
+ answer.replace(match,
+ strlen("UDP/TLS/RTP/SAVPF 121"),
+ "UDP/TLS/RTP/SAVPF 121");
+
+ match = answer.find("\r\na=rtpmap:120 VP8/90000");
+ ASSERT_NE(std::string::npos, match);
+ answer.replace(match,
+ strlen("\r\na=rtpmap:121 VP8/90000"),
+ "\r\na=rtpmap:121 VP8/90000");
+
+ match = answer.find("\r\na=rtcp-fb:120 nack");
+ ASSERT_NE(std::string::npos, match);
+ answer.replace(match,
+ strlen("\r\na=rtcp-fb:121 nack"),
+ "\r\na=rtcp-fb:121 nack");
+
+ match = answer.find("\r\na=rtcp-fb:120 nack pli");
+ ASSERT_NE(std::string::npos, match);
+ answer.replace(match,
+ strlen("\r\na=rtcp-fb:121 nack pli"),
+ "\r\na=rtcp-fb:121 nack pli");
+
+ match = answer.find("\r\na=rtcp-fb:120 ccm fir");
+ ASSERT_NE(std::string::npos, match);
+ answer.replace(match,
+ strlen("\r\na=rtcp-fb:121 ccm fir"),
+ "\r\na=rtcp-fb:121 ccm fir");
+
+ std::cout << "Modified SDP " << std::endl
+ << indent(answer) << std::endl;
+
+ a2_->SetLocal(TestObserver::ANSWER, answer, false);
+
+ ASSERT_EQ(a2_->pObserver->lastStatusCode,
+ PeerConnectionImpl::kNoError);
+
+ a1_->SetRemote(TestObserver::ANSWER, answer, false);
+
+ ASSERT_EQ(a1_->pObserver->lastStatusCode,
+ PeerConnectionImpl::kNoError);
+
+ WaitForCompleted();
+
+ CheckPipelines();
+ CheckStreams();
+
+ CloseStreams();
+}
+
+TEST_P(SignalingTest, VideoNegotiationFails)
+{
+ EnsureInit();
+
+ OfferOptions options;
+
+ a1_->CreateOffer(options, OFFER_AV);
+ a1_->SetLocal(TestObserver::OFFER, a1_->offer());
+
+ ParsedSDP parsedOffer(a1_->offer());
+ parsedOffer.DeleteLines("a=rtcp-fb:120");
+ parsedOffer.DeleteLines("a=rtcp-fb:126");
+ parsedOffer.DeleteLines("a=rtcp-fb:97");
+ parsedOffer.DeleteLines("a=rtpmap:120");
+ parsedOffer.DeleteLines("a=rtpmap:126");
+ parsedOffer.DeleteLines("a=rtpmap:97");
+ parsedOffer.AddLine("a=rtpmap:120 VP9/90000\r\n");
+ parsedOffer.AddLine("a=rtpmap:126 VP10/90000\r\n");
+ parsedOffer.AddLine("a=rtpmap:97 H265/90000\r\n");
+
+ std::cout << "Modified offer: " << std::endl << parsedOffer.getSdp()
+ << std::endl;
+
+ a2_->SetRemote(TestObserver::OFFER, parsedOffer.getSdp(), false);
+ a2_->CreateAnswer(OFFER_AV|ANSWER_AUDIO);
+
+ a2_->SetLocal(TestObserver::ANSWER, a2_->answer(), false);
+
+ ASSERT_EQ(a2_->pObserver->lastStatusCode,
+ PeerConnectionImpl::kNoError);
+
+ a1_->SetRemote(TestObserver::ANSWER, a2_->answer(), false);
+
+ ASSERT_EQ(a1_->pObserver->lastStatusCode,
+ PeerConnectionImpl::kNoError);
+
+ a1_->ExpectMissingTracks(SdpMediaSection::kVideo);
+ a2_->ExpectMissingTracks(SdpMediaSection::kVideo);
+
+ WaitForCompleted();
+
+ CheckPipelines();
+ // TODO: (bug 1140089) a2 is not seeing audio segments in this test.
+ // CheckStreams();
+
+ CloseStreams();
+}
+
+TEST_P(SignalingTest, AudioNegotiationFails)
+{
+ EnsureInit();
+
+ OfferOptions options;
+
+ a1_->CreateOffer(options, OFFER_AV);
+ a1_->SetLocal(TestObserver::OFFER, a1_->offer());
+
+ ParsedSDP parsedOffer(a1_->offer());
+ parsedOffer.ReplaceLine("a=rtpmap:0", "a=rtpmap:0 G728/8000");
+ parsedOffer.ReplaceLine("a=rtpmap:8", "a=rtpmap:8 G729/8000");
+ parsedOffer.ReplaceLine("a=rtpmap:9", "a=rtpmap:9 GSM/8000");
+ parsedOffer.ReplaceLine("a=rtpmap:109", "a=rtpmap:109 LPC/8000");
+
+ a2_->SetRemote(TestObserver::OFFER, parsedOffer.getSdp(), false);
+ a2_->CreateAnswer(OFFER_AV|ANSWER_VIDEO);
+
+ a2_->SetLocal(TestObserver::ANSWER, a2_->answer(), false);
+
+ ASSERT_EQ(a2_->pObserver->lastStatusCode,
+ PeerConnectionImpl::kNoError);
+
+ a1_->SetRemote(TestObserver::ANSWER, a2_->answer(), false);
+
+ ASSERT_EQ(a1_->pObserver->lastStatusCode,
+ PeerConnectionImpl::kNoError);
+
+ a1_->ExpectMissingTracks(SdpMediaSection::kAudio);
+ a2_->ExpectMissingTracks(SdpMediaSection::kAudio);
+
+ WaitForCompleted();
+
+ CheckPipelines();
+ CheckStreams();
+
+ CloseStreams();
+}
+
+TEST_P(SignalingTest, BundleStreamCorrelationBySsrc)
+{
+ if (!UseBundle()) {
+ return;
+ }
+
+ EnsureInit();
+
+ a1_->AddStream(DOMMediaStream::HINT_CONTENTS_AUDIO);
+ a1_->AddStream(DOMMediaStream::HINT_CONTENTS_AUDIO);
+
+ OfferOptions options;
+
+ a1_->CreateOffer(options, OFFER_NONE);
+ ParsedSDP parsedOffer(a1_->offer());
+
+ // Sabotage mid-based matching
+ std::string modifiedOffer = parsedOffer.getSdp();
+ size_t midExtStart =
+ modifiedOffer.find("urn:ietf:params:rtp-hdrext:sdes:mid");
+ if (midExtStart != std::string::npos) {
+ // Just garble it a little
+ modifiedOffer[midExtStart] = 'q';
+ }
+
+ a1_->SetLocal(TestObserver::OFFER, modifiedOffer);
+
+ a2_->SetRemote(TestObserver::OFFER, modifiedOffer, false);
+ a2_->CreateAnswer(ANSWER_AUDIO);
+
+ a2_->SetLocal(TestObserver::ANSWER, a2_->answer(), false);
+
+ ASSERT_EQ(a2_->pObserver->lastStatusCode,
+ PeerConnectionImpl::kNoError);
+
+ a1_->SetRemote(TestObserver::ANSWER, a2_->answer(), false);
+
+ ASSERT_EQ(a1_->pObserver->lastStatusCode,
+ PeerConnectionImpl::kNoError);
+
+ WaitForCompleted();
+
+ CheckPipelines();
+ CheckStreams();
+
+ CloseStreams();
+}
+
+TEST_P(SignalingTest, BundleStreamCorrelationByUniquePt)
+{
+ if (!UseBundle()) {
+ return;
+ }
+
+ EnsureInit();
+
+ OfferOptions options;
+
+ a1_->CreateOffer(options, OFFER_AV);
+ ParsedSDP parsedOffer(a1_->offer());
+
+ std::string modifiedOffer = parsedOffer.getSdp();
+ // Sabotage ssrc matching
+ size_t ssrcStart =
+ modifiedOffer.find("a=ssrc:");
+ ASSERT_NE(std::string::npos, ssrcStart);
+ // Garble
+ modifiedOffer[ssrcStart+2] = 'q';
+
+ // Sabotage mid-based matching
+ size_t midExtStart =
+ modifiedOffer.find("urn:ietf:params:rtp-hdrext:sdes:mid");
+ if (midExtStart != std::string::npos) {
+ // Just garble it a little
+ modifiedOffer[midExtStart] = 'q';
+ }
+
+ a1_->SetLocal(TestObserver::OFFER, modifiedOffer);
+
+ a2_->SetRemote(TestObserver::OFFER, modifiedOffer, false);
+ a2_->CreateAnswer(OFFER_AV|ANSWER_AV);
+
+ a2_->SetLocal(TestObserver::ANSWER, a2_->answer(), false);
+
+ ASSERT_EQ(a2_->pObserver->lastStatusCode,
+ PeerConnectionImpl::kNoError);
+
+ a1_->SetRemote(TestObserver::ANSWER, a2_->answer(), false);
+
+ ASSERT_EQ(a1_->pObserver->lastStatusCode,
+ PeerConnectionImpl::kNoError);
+
+ WaitForCompleted();
+
+ CheckPipelines();
+ CheckStreams();
+
+ CloseStreams();
+}
+
+INSTANTIATE_TEST_CASE_P(Variants, SignalingTest,
+ ::testing::Values("max-bundle",
+ "balanced",
+ "max-compat",
+ "no_bundle",
+ "reject_bundle"));
+
+} // End namespace test.
+
+bool is_color_terminal(const char *terminal) {
+ if (!terminal) {
+ return false;
+ }
+ const char *color_terms[] = {
+ "xterm",
+ "xterm-color",
+ "xterm-256color",
+ "screen",
+ "linux",
+ "cygwin",
+ 0
+ };
+ const char **p = color_terms;
+ while (*p) {
+ if (!strcmp(terminal, *p)) {
+ return true;
+ }
+ p++;
+ }
+ return false;
+}
+
+static std::string get_environment(const char *name) {
+ char *value = getenv(name);
+
+ if (!value)
+ return "";
+
+ return value;
+}
+
+// This exists to send as an event to trigger shutdown.
+static void tests_complete() {
+ gTestsComplete = true;
+}
+
+// The GTest thread runs this instead of the main thread so it can
+// do things like ASSERT_TRUE_WAIT which you could not do on the main thread.
+static int gtest_main(int argc, char **argv) {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ ::testing::InitGoogleTest(&argc, argv);
+
+ for(int i=0; i<argc; i++) {
+ if (!strcmp(argv[i],"-t")) {
+ kDefaultTimeout = 20000;
+ }
+ }
+
+ ::testing::AddGlobalTestEnvironment(new test::SignalingEnvironment);
+ int result = RUN_ALL_TESTS();
+
+ test_utils->sts_target()->Dispatch(
+ WrapRunnableNM(&TestStunServer::ShutdownInstance), NS_DISPATCH_SYNC);
+
+ // Set the global shutdown flag and tickle the main thread
+ // The main thread did not go through Init() so calling Shutdown()
+ // on it will not work.
+ gMainThread->Dispatch(WrapRunnableNM(tests_complete), NS_DISPATCH_SYNC);
+
+ return result;
+}
+
+#ifdef SIGNALING_UNITTEST_STANDALONE
+static void verifyStringTable(const EnumEntry* bindingTable,
+ const char** ourTable)
+{
+ while (bindingTable->value) {
+ if (strcmp(bindingTable->value, *ourTable)) {
+ MOZ_CRASH("Our tables are out of sync with the bindings");
+ }
+ ++bindingTable;
+ ++ourTable;
+ }
+}
+#endif // SIGNALING_UNITTEST_STANDALONE
+
+int main(int argc, char **argv) {
+
+ // This test can cause intermittent oranges on the builders
+ CHECK_ENVIRONMENT_FLAG("MOZ_WEBRTC_TESTS")
+
+ if (isatty(STDOUT_FILENO) && is_color_terminal(getenv("TERM"))) {
+ std::string ansiMagenta = "\x1b[35m";
+ std::string ansiCyan = "\x1b[36m";
+ std::string ansiColorOff = "\x1b[0m";
+ callerName = ansiCyan + callerName + ansiColorOff;
+ calleeName = ansiMagenta + calleeName + ansiColorOff;
+ }
+
+#ifdef SIGNALING_UNITTEST_STANDALONE
+ // Verify our string tables are correct.
+ verifyStringTable(PCImplSignalingStateValues::strings,
+ test::PCImplSignalingStateStrings);
+ verifyStringTable(PCImplIceConnectionStateValues::strings,
+ test::PCImplIceConnectionStateStrings);
+ verifyStringTable(PCImplIceGatheringStateValues::strings,
+ test::PCImplIceGatheringStateStrings);
+#endif // SIGNALING_UNITTEST_STANDALONE
+
+ std::string tmp = get_environment("STUN_SERVER_ADDRESS");
+ if (tmp != "")
+ g_stun_server_address = tmp;
+
+ tmp = get_environment("STUN_SERVER_PORT");
+ if (tmp != "")
+ g_stun_server_port = atoi(tmp.c_str());
+
+ test_utils = new MtransportTestUtils();
+ NSS_NoDB_Init(nullptr);
+ NSS_SetDomesticPolicy();
+
+ ::testing::TestEventListeners& listeners =
+ ::testing::UnitTest::GetInstance()->listeners();
+ // Adds a listener to the end. Google Test takes the ownership.
+ listeners.Append(new test::RingbufferDumper(test_utils));
+ test_utils->sts_target()->Dispatch(
+ WrapRunnableNM(&TestStunServer::GetInstance, AF_INET), NS_DISPATCH_SYNC);
+
+ // Set the main thread global which is this thread.
+ nsIThread *thread;
+ NS_GetMainThread(&thread);
+ gMainThread = thread;
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Now create the GTest thread and run all of the tests on it
+ // When it is complete it will set gTestsComplete
+ NS_NewNamedThread("gtest_thread", &thread);
+ gGtestThread = thread;
+
+ int result;
+ gGtestThread->Dispatch(
+ WrapRunnableNMRet(&result, gtest_main, argc, argv), NS_DISPATCH_NORMAL);
+
+ // Here we handle the event queue for dispatches to the main thread
+ // When the GTest thread is complete it will send one more dispatch
+ // with gTestsComplete == true.
+ while (!gTestsComplete && NS_ProcessNextEvent());
+
+ gGtestThread->Shutdown();
+
+ PeerConnectionCtx::Destroy();
+ delete test_utils;
+
+ return result;
+}