blob: 7b4d3a05eeb155da6666ffc5d78e61f9adfce780 [file] [log] [blame]
/*
* libjingle
* Copyright 2011, Google Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <map>
#include <string>
#include <utility>
#include "talk/app/webrtc/audiotrackimpl.h"
#include "talk/app/webrtc/mediastreamimpl.h"
#include "talk/app/webrtc/peerconnectionsignaling.h"
#include "talk/app/webrtc/sessiondescriptionprovider.h"
#include "talk/app/webrtc/streamcollectionimpl.h"
#include "talk/app/webrtc/videotrackimpl.h"
#include "talk/base/gunit.h"
#include "talk/base/scoped_ptr.h"
#include "talk/base/thread.h"
#include "talk/session/phone/channelmanager.h"
static const char kStreamLabel1[] = "local_stream_1";
static const char kStreamLabel2[] = "local_stream_2";
static const char kAudioTrackLabel1[] = "local_audio_1";
static const char kAudioTrackLabel2[] = "local_audio_2";
static const char kVideoTrackLabel1[] = "local_video_1";
namespace webrtc {
typedef std::map<std::string, talk_base::scoped_refptr<MediaStreamInterface> >
MediaStreamMap;
typedef std::pair<std::string, talk_base::scoped_refptr<MediaStreamInterface> >
RemotePair;
// MockSignalingObserver implements functions for listening all signals from a
// PeerConnectionSignaling instance.
// The method AnswerPeer can be used to forward messages from one
// PeerConnectionSignaling instance to another.
class MockSignalingObserver : public sigslot::has_slots<> {
public:
MockSignalingObserver()
: last_error_(-1), // Initialize last_error_ to unused error code.
state_(PeerConnectionSignaling::kInitializing),
remote_peer_(NULL) {
}
virtual ~MockSignalingObserver() {}
// New remote stream have been discovered.
virtual void OnRemoteStreamAdded(MediaStreamInterface* remote_stream) {
EXPECT_EQ(MediaStreamInterface::kLive, remote_stream->ready_state());
remote_media_streams_.insert(RemotePair(remote_stream->label(),
remote_stream));
}
// Remote stream is no longer available.
virtual void OnRemoteStreamRemoved(MediaStreamInterface* remote_stream) {
EXPECT_TRUE(remote_media_streams_.find(remote_stream->label()) !=
remote_media_streams_.end());
EXPECT_EQ(MediaStreamInterface::kEnded, remote_stream->ready_state());
remote_media_streams_.erase(remote_stream->label());
}
virtual void OnStateChange(PeerConnectionSignaling::State state) {
state_ = state;
}
virtual void OnErrorReceived(RoapErrorCode error) {
last_error_ = error;
}
void OnSignalingMessage(const std::string& smessage) {
last_message_ = smessage;
if (remote_peer_) {
remote_peer_->ProcessSignalingMessage(smessage, remote_local_collection_);
}
}
// Tell this object to answer the remote_peer.
// remote_local_collection is the local collection the remote peer want to
// send in an answer.
void AnswerPeer(PeerConnectionSignaling* remote_peer,
StreamCollection* remote_local_collection) {
remote_peer_ = remote_peer;
remote_local_collection_ = remote_local_collection;
}
void CancelAnswerPeer() {
remote_peer_ = NULL;
remote_local_collection_.release();
}
MediaStreamInterface* RemoteStream(const std::string& label) {
MediaStreamMap::iterator it = remote_media_streams_.find(label);
if (it != remote_media_streams_.end())
return it->second;
return NULL;
}
std::string last_message_;
int last_error_;
PeerConnectionSignaling::State state_;
private:
MediaStreamMap remote_media_streams_;
talk_base::scoped_refptr<StreamCollection> remote_local_collection_;
PeerConnectionSignaling* remote_peer_;
};
// Mock implementation of SessionDescriptionProvider.
// PeerConnectionSignaling uses this object to create session descriptions.
class MockSessionDescriptionProvider : public SessionDescriptionProvider {
public:
explicit MockSessionDescriptionProvider(
cricket::ChannelManager* channel_manager)
: update_session_description_counter_(0),
session_description_factory_(
new cricket::MediaSessionDescriptionFactory(channel_manager)) {
}
virtual const cricket::SessionDescription* ProvideOffer(
const cricket::MediaSessionOptions& options) {
local_desc_.reset(session_description_factory_->CreateOffer(
options, local_desc_.get()));
return local_desc_.get();
}
virtual const cricket::SessionDescription* SetRemoteSessionDescription(
const cricket::SessionDescription* remote_offer,
const cricket::Candidates& remote_candidates) {
remote_desc_.reset(remote_offer);
return remote_desc_.get();
}
virtual const cricket::SessionDescription* ProvideAnswer(
const cricket::MediaSessionOptions& options) {
local_desc_.reset(session_description_factory_->CreateAnswer(
remote_desc_.get(), options, local_desc_.get()));
return local_desc_.get();
}
virtual void NegotiationDone() {
++update_session_description_counter_;
}
size_t update_session_description_counter_;
protected:
talk_base::scoped_ptr<cricket::MediaSessionDescriptionFactory>
session_description_factory_;
talk_base::scoped_ptr<const cricket::SessionDescription> local_desc_;
talk_base::scoped_ptr<const cricket::SessionDescription> remote_desc_;
};
// PeerConnectionSignalingTest create two PeerConnectionSignaling instances
// and connects the signals to two MockSignalingObservers.
// This is used in tests to test the signaling between to peers.
class PeerConnectionSignalingTest: public testing::Test {
protected:
virtual void SetUp() {
channel_manager_.reset(new cricket::ChannelManager(
talk_base::Thread::Current()));
EXPECT_TRUE(channel_manager_->Init());
provider1_.reset(new MockSessionDescriptionProvider(
channel_manager_.get()));
provider2_.reset(new MockSessionDescriptionProvider(
channel_manager_.get()));
signaling1_.reset(new PeerConnectionSignaling(
talk_base::Thread::Current(), provider1_.get()));
observer1_.reset(new MockSignalingObserver());
signaling1_->SignalNewPeerConnectionMessage.connect(
observer1_.get(), &MockSignalingObserver::OnSignalingMessage);
signaling1_->SignalRemoteStreamAdded.connect(
observer1_.get(), &MockSignalingObserver::OnRemoteStreamAdded);
signaling1_->SignalRemoteStreamRemoved.connect(
observer1_.get(), &MockSignalingObserver::OnRemoteStreamRemoved);
signaling1_->SignalErrorMessageReceived.connect(
observer1_.get(), &MockSignalingObserver::OnErrorReceived);
signaling1_->SignalStateChange.connect(
observer1_.get(), &MockSignalingObserver::OnStateChange);
signaling2_.reset(new PeerConnectionSignaling(
talk_base::Thread::Current(), provider2_.get()));
observer2_.reset(new MockSignalingObserver());
signaling2_->SignalNewPeerConnectionMessage.connect(
observer2_.get(), &MockSignalingObserver::OnSignalingMessage);
signaling2_->SignalRemoteStreamAdded.connect(
observer2_.get(), &MockSignalingObserver::OnRemoteStreamAdded);
signaling2_->SignalRemoteStreamRemoved.connect(
observer2_.get(), &MockSignalingObserver::OnRemoteStreamRemoved);
signaling2_->SignalErrorMessageReceived.connect(
observer2_.get(), &MockSignalingObserver::OnErrorReceived);
signaling2_->SignalStateChange.connect(
observer2_.get(), &MockSignalingObserver::OnStateChange);
}
// Create a collection of streams be sent on signaling1__
talk_base::scoped_refptr<StreamCollection> CreateLocalCollection1() {
std::string label(kStreamLabel1);
talk_base::scoped_refptr<LocalMediaStreamInterface> stream1(
MediaStream::Create(label));
// Add a local audio track.
talk_base::scoped_refptr<LocalAudioTrackInterface>
audio_track(AudioTrack::CreateLocal(kAudioTrackLabel1, NULL));
stream1->AddTrack(audio_track);
// Add a local video track.
talk_base::scoped_refptr<LocalVideoTrackInterface>
video_track(VideoTrack::CreateLocal(kVideoTrackLabel1, NULL));
stream1->AddTrack(video_track);
talk_base::scoped_refptr<StreamCollection> local_collection1(
StreamCollection::Create());
local_collection1->AddStream(stream1);
return local_collection1;
}
talk_base::scoped_refptr<StreamCollection> CreateLocalCollection2() {
std::string label(kStreamLabel2);
talk_base::scoped_refptr<LocalMediaStreamInterface> stream2(
MediaStream::Create(label));
// Add a local audio track.
talk_base::scoped_refptr<LocalAudioTrackInterface>
audio_track(AudioTrack::CreateLocal(kAudioTrackLabel2, NULL));
stream2->AddTrack(audio_track);
talk_base::scoped_refptr<StreamCollection> local_collection2(
StreamCollection::Create());
local_collection2->AddStream(stream2);
return local_collection2;
}
void VerifyStreamStates(StreamCollection* collection,
MediaStreamInterface::ReadyState state,
MediaStreamTrackInterface::TrackState track_state) {
for (size_t i = 0; i < collection->count(); ++i) {
MediaStreamInterface* stream = collection->at(i);
EXPECT_EQ(state, stream->ready_state());
for (size_t j = 0; j < stream->audio_tracks()->count(); ++j) {
AudioTrackInterface* audio = stream->audio_tracks()->at(j);
EXPECT_EQ(track_state, audio->state());
}
for (size_t j = 0; j < stream->video_tracks()->count(); ++j) {
VideoTrackInterface* video = stream->video_tracks()->at(j);
EXPECT_EQ(track_state, video->state());
}
}
}
// Initialize and setup a simple call between signaling1_ and signaling2_.
// signaling1_ send stream with label kStreamLabel1 to signaling2_.
void SetUpOneWayCall() {
// Initialize signaling1_ and signaling_2 by providing the candidates.
signaling1_->OnCandidatesReady(candidates_);
signaling2_->OnCandidatesReady(candidates_);
// Create a local stream collection to be sent on signaling1_.
talk_base::scoped_refptr<StreamCollection> local_collection1(
CreateLocalCollection1());
talk_base::scoped_refptr<StreamCollection> local_collection2(
StreamCollection::Create());
// Connect all messages sent from signaling1_ to be received on signaling2_
observer1_->AnswerPeer(signaling2_.get(), local_collection2);
// Connect all messages sent from Peer2 to be received on Peer1
observer2_->AnswerPeer(signaling1_.get(), local_collection1);
signaling1_->CreateOffer(local_collection1);
EXPECT_EQ(PeerConnectionSignaling::kWaitingForAnswer,
signaling1_->GetState());
EXPECT_EQ(PeerConnectionSignaling::kIdle, signaling2_->GetState());
// Process posted messages to generate the offer and the answer to the
// offer.
talk_base::Thread::Current()->ProcessMessages(1);
talk_base::Thread::Current()->ProcessMessages(1);
// Make sure all is setup.
EXPECT_EQ(PeerConnectionSignaling::kIdle, signaling1_->GetState());
EXPECT_EQ(PeerConnectionSignaling::kIdle, signaling2_->GetState());
EXPECT_TRUE(observer2_->RemoteStream(kStreamLabel1) != NULL);
EXPECT_EQ(0u, signaling1_->remote_streams()->count());
EXPECT_EQ(1u, signaling2_->remote_streams()->count());
}
cricket::Candidates candidates_;
talk_base::scoped_ptr<MockSignalingObserver> observer1_;
talk_base::scoped_ptr<MockSignalingObserver> observer2_;
talk_base::scoped_ptr<MockSessionDescriptionProvider> provider1_;
talk_base::scoped_ptr<MockSessionDescriptionProvider> provider2_;
talk_base::scoped_ptr<PeerConnectionSignaling> signaling1_;
talk_base::scoped_ptr<PeerConnectionSignaling> signaling2_;
talk_base::scoped_ptr<cricket::ChannelManager> channel_manager_;
};
TEST_F(PeerConnectionSignalingTest, SimpleOneWayCall) {
// Peer 1 create an offer with only one audio track.
talk_base::scoped_refptr<StreamCollection> local_collection1(
CreateLocalCollection1());
// Verify that the local stream is now initializing.
VerifyStreamStates(local_collection1.get(),
MediaStreamInterface::kInitializing,
MediaStreamTrackInterface::kInitializing);
// Peer 2 only receive. Create an empty collection
talk_base::scoped_refptr<StreamCollection> local_collection2(
StreamCollection::Create());
// Connect all messages sent from Peer1 to be received on Peer2
observer1_->AnswerPeer(signaling2_.get(), local_collection2);
// Connect all messages sent from Peer2 to be received on Peer1
observer2_->AnswerPeer(signaling1_.get(), local_collection1);
// Peer 1 generates the offer. It is not sent since there is no
// local candidates ready.
signaling1_->CreateOffer(local_collection1);
// Process posted messages.
talk_base::Thread::Current()->ProcessMessages(1);
EXPECT_EQ(PeerConnectionSignaling::kInitializing, signaling1_->GetState());
// Initialize signaling1_ by providing the candidates.
signaling1_->OnCandidatesReady(candidates_);
EXPECT_EQ(PeerConnectionSignaling::kWaitingForAnswer,
signaling1_->GetState());
// Process posted messages to allow signaling_1 to send the offer.
talk_base::Thread::Current()->ProcessMessages(1);
// Verify that signaling_2 is still not initialized.
// Even though it have received an offer.
EXPECT_EQ(PeerConnectionSignaling::kInitializing, signaling2_->GetState());
// Provide the candidates to signaling_2 and let it process the offer.
signaling2_->OnCandidatesReady(candidates_);
talk_base::Thread::Current()->ProcessMessages(1);
// Verify that the offer/answer have been exchanged and the state is good.
EXPECT_EQ(PeerConnectionSignaling::kIdle, signaling1_->GetState());
EXPECT_EQ(PeerConnectionSignaling::kIdle, signaling2_->GetState());
// Verify that the local stream is now sending.
VerifyStreamStates(local_collection1, MediaStreamInterface::kLive,
MediaStreamTrackInterface::kLive);
// Verify that PeerConnection2 is aware of the sending stream.
EXPECT_TRUE(observer2_->RemoteStream(kStreamLabel1) != NULL);
// Verify that both peers have updated the session descriptions.
EXPECT_EQ(1u, provider1_->update_session_description_counter_);
EXPECT_EQ(1u, provider2_->update_session_description_counter_);
}
TEST_F(PeerConnectionSignalingTest, Glare) {
// Setup a call.
SetUpOneWayCall();
// Stop sending all messages automatically between Peer 1 and Peer 2.
observer1_->CancelAnswerPeer();
observer2_->CancelAnswerPeer();
// Create an empty collection for Peer 1.
talk_base::scoped_refptr<StreamCollection> local_collection1(
StreamCollection::Create());
// Create a collection for Peer 2.
talk_base::scoped_refptr<StreamCollection> local_collection2(
CreateLocalCollection2());
// Peer 1 create an updated offer.
signaling1_->CreateOffer(local_collection1);
// Peer 2 create an updated offer.
signaling2_->CreateOffer(local_collection2);
// Process posted messages.
talk_base::Thread::Current()->ProcessMessages(1);
talk_base::Thread::Current()->ProcessMessages(1);
std::string offer_1 = observer1_->last_message_;
std::string offer_2 = observer2_->last_message_;
EXPECT_EQ(PeerConnectionSignaling::kWaitingForAnswer,
signaling1_->GetState());
EXPECT_EQ(PeerConnectionSignaling::kWaitingForAnswer,
signaling2_->GetState());
// Connect all messages sent from Peer 1 to be received on Peer 2
observer1_->AnswerPeer(signaling2_.get(), local_collection2);
// Connect all messages sent from Peer 2 to be received on Peer 1
observer2_->AnswerPeer(signaling1_.get(), local_collection1);
// Insert the two offers to each Peer to create the Glare.
signaling1_->ProcessSignalingMessage(offer_2, local_collection1);
signaling2_->ProcessSignalingMessage(offer_1, local_collection2);
talk_base::Thread::Current()->ProcessMessages(1);
talk_base::Thread::Current()->ProcessMessages(1);
// Make sure all is good.
EXPECT_EQ(PeerConnectionSignaling::kIdle, signaling1_->GetState());
EXPECT_EQ(PeerConnectionSignaling::kIdle, signaling2_->GetState());
// Verify that Peer 1 is receiving kStreamLabel2.
EXPECT_TRUE(observer1_->RemoteStream(kStreamLabel2) != NULL);
// Verify that Peer 2 don't receive any streams
// since it has been removed.
EXPECT_TRUE(observer2_->RemoteStream(kStreamLabel1) == NULL);
// Verify that both peers have updated the session descriptions.
EXPECT_EQ(2u, provider1_->update_session_description_counter_);
EXPECT_EQ(2u, provider2_->update_session_description_counter_);
}
TEST_F(PeerConnectionSignalingTest, AddRemoveStream) {
// Initialize signaling1_ and signaling_2 by providing the candidates.
signaling1_->OnCandidatesReady(candidates_);
signaling2_->OnCandidatesReady(candidates_);
// Create a local stream.
std::string label(kStreamLabel1);
talk_base::scoped_refptr<LocalMediaStreamInterface> stream(
MediaStream::Create(label));
// Add a local audio track.
talk_base::scoped_refptr<LocalAudioTrackInterface>
audio_track(AudioTrack::CreateLocal(kAudioTrackLabel1, NULL));
stream->AddTrack(audio_track);
// Add a local video track.
talk_base::scoped_refptr<LocalVideoTrackInterface>
video_track(VideoTrack::CreateLocal(kVideoTrackLabel1, NULL));
stream->AddTrack(video_track);
// Peer 1 create an empty collection
talk_base::scoped_refptr<StreamCollection> local_collection1(
StreamCollection::Create());
// Peer 2 create an empty collection
talk_base::scoped_refptr<StreamCollection> local_collection2(
StreamCollection::Create());
// Connect all messages sent from Peer1 to be received on Peer2
observer1_->AnswerPeer(signaling2_.get(), local_collection2);
// Connect all messages sent from Peer2 to be received on Peer1
observer2_->AnswerPeer(signaling1_.get(), local_collection1);
// Peer 1 creates an empty offer and send it to Peer2.
signaling1_->CreateOffer(local_collection1);
// Process posted messages.
talk_base::Thread::Current()->ProcessMessages(1);
talk_base::Thread::Current()->ProcessMessages(1);
// Verify that both peers have updated the session descriptions.
EXPECT_EQ(1u, provider1_->update_session_description_counter_);
EXPECT_EQ(1u, provider2_->update_session_description_counter_);
// Peer2 add a stream.
local_collection2->AddStream(stream);
signaling2_->CreateOffer(local_collection2);
talk_base::Thread::Current()->ProcessMessages(1);
talk_base::Thread::Current()->ProcessMessages(1);
// Verify that the PeerConnection 2 local stream is now sending.
VerifyStreamStates(local_collection2, MediaStreamInterface::kLive ,
MediaStreamTrackInterface::kLive);
// Verify that PeerConnection1 is aware of the sending stream.
EXPECT_TRUE(observer1_->RemoteStream(label) != NULL);
// Verify that both peers have updated the session descriptions.
EXPECT_EQ(2u, provider1_->update_session_description_counter_);
EXPECT_EQ(2u, provider2_->update_session_description_counter_);
// Remove the stream
local_collection2->RemoveStream(stream);
signaling2_->CreateOffer(local_collection2);
talk_base::Thread::Current()->ProcessMessages(1);
talk_base::Thread::Current()->ProcessMessages(1);
// Verify that PeerConnection1 is not aware of the sending stream.
EXPECT_TRUE(observer1_->RemoteStream(label) == NULL);
// Verify that the PeerConnection 2 local stream is now ended.
VerifyStreamStates(local_collection2, MediaStreamInterface::kEnded ,
MediaStreamTrackInterface::kEnded);
// Verify that both peers have updated the session descriptions.
EXPECT_EQ(3u, provider1_->update_session_description_counter_);
EXPECT_EQ(3u, provider2_->update_session_description_counter_);
}
TEST_F(PeerConnectionSignalingTest, ShutDown) {
// Setup a call.
SetUpOneWayCall();
signaling1_->SendShutDown();
EXPECT_EQ_WAIT(PeerConnectionSignaling::kShutdownComplete,
signaling1_->GetState(), 10);
EXPECT_EQ_WAIT(PeerConnectionSignaling::kShutdownComplete,
signaling2_->GetState(), 10);
EXPECT_EQ(0u, signaling1_->remote_streams()->count());
EXPECT_EQ(0u, signaling2_->remote_streams()->count());
EXPECT_TRUE(observer2_->RemoteStream(kStreamLabel1) == NULL);
EXPECT_EQ(PeerConnectionSignaling::kShutdownComplete, observer1_->state_);
EXPECT_EQ(PeerConnectionSignaling::kShutdownComplete, observer2_->state_);
// Verify that both peers have updated the session descriptions.
EXPECT_EQ(2u, provider1_->update_session_description_counter_);
EXPECT_EQ(2u, provider2_->update_session_description_counter_);
}
TEST_F(PeerConnectionSignalingTest, ReceiveError) {
// Initialize signaling1_
signaling1_->OnCandidatesReady(candidates_);
talk_base::scoped_refptr<StreamCollection> local_collection1(
CreateLocalCollection1());
signaling1_->CreateOffer(local_collection1);
talk_base::Thread::Current()->ProcessMessages(1);
EXPECT_EQ(PeerConnectionSignaling::kWaitingForAnswer,
signaling1_->GetState());
RoapSession roap_session;
roap_session.Parse(observer1_->last_message_);
signaling1_->ProcessSignalingMessage(roap_session.CreateErrorMessage(
kNoMatch), local_collection1);
EXPECT_EQ(kNoMatch, observer1_->last_error_);
// Check signaling have cleaned up.
EXPECT_EQ(PeerConnectionSignaling::kIdle, signaling1_->GetState());
signaling1_->CreateOffer(local_collection1);
talk_base::Thread::Current()->ProcessMessages(1);
EXPECT_EQ(PeerConnectionSignaling::kWaitingForAnswer,
signaling1_->GetState());
}
} // namespace webrtc