diff options
Diffstat (limited to 'media/webrtc/signaling/test')
-rw-r--r-- | media/webrtc/signaling/test/FakeIPC.cpp | 35 | ||||
-rw-r--r-- | media/webrtc/signaling/test/FakeIPC.h | 22 | ||||
-rw-r--r-- | media/webrtc/signaling/test/FakeLogging.h | 26 | ||||
-rw-r--r-- | media/webrtc/signaling/test/FakeMediaStreams.h | 656 | ||||
-rw-r--r-- | media/webrtc/signaling/test/FakeMediaStreamsImpl.h | 236 | ||||
-rw-r--r-- | media/webrtc/signaling/test/FakePCObserver.h | 112 | ||||
-rw-r--r-- | media/webrtc/signaling/test/common.build | 134 | ||||
-rw-r--r-- | media/webrtc/signaling/test/jsep_session_unittest.cpp | 4235 | ||||
-rw-r--r-- | media/webrtc/signaling/test/jsep_track_unittest.cpp | 1269 | ||||
-rw-r--r-- | media/webrtc/signaling/test/mediaconduit_unittests.cpp | 1091 | ||||
-rw-r--r-- | media/webrtc/signaling/test/mediapipeline_unittest.cpp | 720 | ||||
-rw-r--r-- | media/webrtc/signaling/test/moz.build | 33 | ||||
-rw-r--r-- | media/webrtc/signaling/test/sdp_file_parser.cpp | 85 | ||||
-rw-r--r-- | media/webrtc/signaling/test/sdp_unittests.cpp | 5377 | ||||
-rw-r--r-- | media/webrtc/signaling/test/signaling_unittests.cpp | 4851 |
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; +} |