Browse Source

Added signaling state to PeerConnection

Paul-Louis Ageneau 4 years ago
parent
commit
0c47c66bb1
5 changed files with 189 additions and 62 deletions
  1. 7 7
      include/rtc/description.hpp
  2. 18 6
      include/rtc/peerconnection.hpp
  3. 8 0
      include/rtc/rtc.h
  4. 34 16
      src/description.cpp
  5. 122 33
      src/peerconnection.cpp

+ 7 - 7
include/rtc/description.hpp

@@ -34,8 +34,8 @@ namespace rtc {
 
 class Description {
 public:
-	enum class Type { Unspec = 0, Offer = 1, Answer = 2 };
-	enum class Role { ActPass = 0, Passive = 1, Active = 2 };
+	enum class Type { Unspec, Offer, Answer, Pranswer, Rollback };
+	enum class Role { ActPass, Passive, Active };
 	enum class Direction { SendOnly, RecvOnly, SendRecv, Inactive, Unknown };
 
 	Description(const string &sdp, const string &typeString = "");
@@ -45,7 +45,6 @@ public:
 	Type type() const;
 	string typeString() const;
 	Role role() const;
-	string roleString() const;
 	string bundleMid() const;
 	string iceUfrag() const;
 	string icePwd() const;
@@ -193,6 +192,9 @@ public:
 
 	Application *application();
 
+	static Type stringToType(const string &typeString);
+	static string typeToString(Type type);
+
 private:
 	std::optional<Candidate> defaultCandidate() const;
 	std::shared_ptr<Entry> createEntry(string mline, string mid, Direction dir);
@@ -214,14 +216,12 @@ private:
 	// Candidates
 	std::vector<Candidate> mCandidates;
 	bool mEnded = false;
-
-	static Type stringToType(const string &typeString);
-	static string typeToString(Type type);
-	static string roleToString(Role role);
 };
 
 } // namespace rtc
 
 std::ostream &operator<<(std::ostream &out, const rtc::Description &description);
+std::ostream &operator<<(std::ostream &out, rtc::Description::Type type);
+std::ostream &operator<<(std::ostream &out, rtc::Description::Role role);
 
 #endif

+ 18 - 6
include/rtc/peerconnection.hpp

@@ -67,6 +67,14 @@ public:
 		Complete = RTC_GATHERING_COMPLETE
 	};
 
+	enum class SignalingState : int {
+		Stable = RTC_SIGNALING_STABLE,
+		HaveLocalOffer = RTC_SIGNALING_HAVE_LOCAL_OFFER,
+		HaveRemoteOffer = RTC_SIGNALING_HAVE_REMOTE_OFFER,
+		HaveLocalPranswer = RTC_SIGNALING_HAVE_LOCAL_PRANSWER,
+		HaveRemotePranswer = RTC_SIGNALING_HAVE_REMOTE_PRANSWER,
+	} rtcSignalingState;
+
 	PeerConnection(void);
 	PeerConnection(const Configuration &config);
 	~PeerConnection();
@@ -76,6 +84,7 @@ public:
 	const Configuration *config() const;
 	State state() const;
 	GatheringState gatheringState() const;
+	SignalingState signalingState() const;
 	bool hasLocalDescription() const;
 	bool hasRemoteDescription() const;
 	bool hasMedia() const;
@@ -83,8 +92,9 @@ public:
 	std::optional<Description> remoteDescription() const;
 	std::optional<string> localAddress() const;
 	std::optional<string> remoteAddress() const;
+	bool getSelectedCandidatePair(Candidate *local, Candidate *remote);
 
-	void setLocalDescription();
+	void setLocalDescription(Description::Type type = Description::Type::Unspec);
 	void setRemoteDescription(Description description);
 	void addRemoteCandidate(Candidate candidate);
 
@@ -100,6 +110,7 @@ public:
 	void onLocalCandidate(std::function<void(Candidate candidate)> callback);
 	void onStateChange(std::function<void(State state)> callback);
 	void onGatheringStateChange(std::function<void(GatheringState state)> callback);
+	void onSignalingStateChange(std::function<void(SignalingState state)> callback);
 
 	// Stats
 	void clearStats();
@@ -111,9 +122,6 @@ public:
 	std::shared_ptr<Track> addTrack(Description::Media description);
 	void onTrack(std::function<void(std::shared_ptr<Track> track)> callback);
 
-	// libnice only
-	bool getSelectedCandidatePair(Candidate *local, Candidate *remote);
-
 private:
 	std::shared_ptr<IceTransport> initIceTransport(Description::Role role);
 	std::shared_ptr<DtlsTransport> initDtlsTransport();
@@ -143,6 +151,7 @@ private:
 	void triggerTrack(std::shared_ptr<Track> track);
 	bool changeState(State state);
 	bool changeGatheringState(GatheringState state);
+	bool changeSignalingState(SignalingState state);
 
 	void resetCallbacks();
 
@@ -168,6 +177,7 @@ private:
 
 	std::atomic<State> mState;
 	std::atomic<GatheringState> mGatheringState;
+	std::atomic<SignalingState> mSignalingState;
 	std::atomic<bool> mNegociationNeeded;
 
 	synchronized_callback<std::shared_ptr<DataChannel>> mDataChannelCallback;
@@ -175,12 +185,14 @@ private:
 	synchronized_callback<Candidate> mLocalCandidateCallback;
 	synchronized_callback<State> mStateChangeCallback;
 	synchronized_callback<GatheringState> mGatheringStateChangeCallback;
+	synchronized_callback<SignalingState> mSignalingStateChangeCallback;
 	synchronized_callback<std::shared_ptr<Track>> mTrackCallback;
 };
 
 } // namespace rtc
 
-std::ostream &operator<<(std::ostream &out, const rtc::PeerConnection::State &state);
-std::ostream &operator<<(std::ostream &out, const rtc::PeerConnection::GatheringState &state);
+std::ostream &operator<<(std::ostream &out, rtc::PeerConnection::State state);
+std::ostream &operator<<(std::ostream &out, rtc::PeerConnection::GatheringState state);
+std::ostream &operator<<(std::ostream &out, rtc::PeerConnection::SignalingState state);
 
 #endif

+ 8 - 0
include/rtc/rtc.h

@@ -59,6 +59,14 @@ typedef enum {
 	RTC_GATHERING_COMPLETE = 2
 } rtcGatheringState;
 
+typedef enum {
+	RTC_SIGNALING_STABLE = 0,
+	RTC_SIGNALING_HAVE_LOCAL_OFFER = 1,
+	RTC_SIGNALING_HAVE_REMOTE_OFFER = 2,
+	RTC_SIGNALING_HAVE_LOCAL_PRANSWER = 3,
+	RTC_SIGNALING_HAVE_REMOTE_PRANSWER = 4,
+} rtcSignalingState;
+
 typedef enum { // Don't change, it must match plog severity
 	RTC_LOG_NONE = 0,
 	RTC_LOG_FATAL = 1,

+ 34 - 16
src/description.cpp

@@ -26,6 +26,7 @@
 #include <iostream>
 #include <random>
 #include <sstream>
+#include <unordered_map>
 
 using std::shared_ptr;
 using std::size_t;
@@ -767,12 +768,14 @@ Description::Video::Video(string mid, Direction dir)
     : Media("video 9 UDP/TLS/RTP/SAVPF", std::move(mid), dir) {}
 
 Description::Type Description::stringToType(const string &typeString) {
-	if (typeString == "offer")
-		return Type::Offer;
-	else if (typeString == "answer")
-		return Type::Answer;
-	else
-		return Type::Unspec;
+	using TypeMap_t = std::unordered_map<string, Type>;	
+	static const TypeMap_t TypeMap = {{"unspec", Type::Unspec},
+									  {"offer", Type::Offer},
+	                                  {"answer", Type::Pranswer},
+	                                  {"pranswer", Type::Pranswer},
+	                                  {"rollback", Type::Rollback}};
+	auto it = TypeMap.find(typeString); 
+	return it != TypeMap.end() ? it->second : Type::Unspec;
 }
 
 string Description::typeToString(Type type) {
@@ -781,24 +784,39 @@ string Description::typeToString(Type type) {
 		return "offer";
 	case Type::Answer:
 		return "answer";
+	case Type::Pranswer:
+		return "pranswer";
+	case Type::Rollback:
+		return "rollback";
 	default:
-		return "";
+		return "unknown";
 	}
 }
 
-string Description::roleToString(Role role) {
+} // namespace rtc
+
+std::ostream &operator<<(std::ostream &out, const rtc::Description &description) {
+	return out << std::string(description);
+}
+
+std::ostream &operator<<(std::ostream &out, rtc::Description::Type type) {
+	return out << rtc::Description::typeToString(type);
+}
+
+std::ostream &operator<<(std::ostream &out, rtc::Description::Role role) {
+	using Role = rtc::Description::Role;
+	const char *str;
 	switch (role) {
 	case Role::Active:
-		return "active";
+		str = "active";
+		break;
 	case Role::Passive:
-		return "passive";
+		str = "passive";
+		break;
 	default:
-		return "actpass";
+		str = "actpass";
+		break;
 	}
+	return out << str;
 }
 
-} // namespace rtc
-
-std::ostream &operator<<(std::ostream &out, const rtc::Description &description) {
-	return out << std::string(description);
-}

+ 122 - 33
src/peerconnection.cpp

@@ -44,7 +44,8 @@ PeerConnection::PeerConnection() : PeerConnection(Configuration()) {}
 
 PeerConnection::PeerConnection(const Configuration &config)
     : mConfig(config), mCertificate(make_certificate()), mProcessor(std::make_unique<Processor>()),
-      mState(State::New), mGatheringState(GatheringState::New), mNegociationNeeded(false) {
+      mState(State::New), mGatheringState(GatheringState::New),
+      mSignalingState(SignalingState::Stable), mNegociationNeeded(false) {
 	PLOG_VERBOSE << "Creating PeerConnection";
 
 	if (config.portRangeEnd && config.portRangeBegin > config.portRangeEnd)
@@ -74,6 +75,8 @@ PeerConnection::State PeerConnection::state() const { return mState; }
 
 PeerConnection::GatheringState PeerConnection::gatheringState() const { return mGatheringState; }
 
+PeerConnection::SignalingState PeerConnection::signalingState() const { return mSignalingState; }
+
 std::optional<Description> PeerConnection::localDescription() const {
 	std::lock_guard lock(mLocalDescriptionMutex);
 	return mLocalDescription;
@@ -99,7 +102,7 @@ bool PeerConnection::hasMedia() const {
 	return local && local->hasAudioOrVideo();
 }
 
-void PeerConnection::setLocalDescription() {
+void PeerConnection::setLocalDescription(Description::Type type) {
 	PLOG_VERBOSE << "Setting local description";
 
 	if (!mNegociationNeeded.exchange(false)) {
@@ -107,13 +110,30 @@ void PeerConnection::setLocalDescription() {
 		return;
 	}
 
-	// RFC 5763: The endpoint that is the offerer MUST use the setup attribute value of
-	// setup:actpass.
-	// See https://tools.ietf.org/html/rfc5763#section-5
-	auto iceTransport = initIceTransport(Description::Role::ActPass);
-	Description localDescription = iceTransport->getLocalDescription(Description::Type::Offer);
+	// Guess the description type if unspecified
+	if (type == Description::Type::Unspec) {
+		if (mSignalingState == SignalingState::HaveRemoteOffer)
+			type = Description::Type::Answer;
+		else
+			type = Description::Type::Offer;
+	}
+
+	auto iceTransport = std::atomic_load(&mIceTransport);
+	if (!iceTransport) {
+		if (type != Description::Type::Offer) {
+			// RFC 5763: The endpoint that is the offerer MUST use the setup attribute value of
+			// setup:actpass.
+			// See https://tools.ietf.org/html/rfc5763#section-5
+			if (!iceTransport)
+				iceTransport = initIceTransport(Description::Role::ActPass);
+		}
+	}
+
+	Description localDescription = iceTransport->getLocalDescription(type);
 	processLocalDescription(localDescription);
-	iceTransport->gatherLocalCandidates();
+
+	if (mGatheringState == GatheringState::New)
+		iceTransport->gatherLocalCandidates();
 }
 
 void PeerConnection::setRemoteDescription(Description description) {
@@ -143,21 +163,40 @@ void PeerConnection::setRemoteDescription(Description description) {
 			throw std::logic_error("Got the local description as remote description");
 	}
 
-	description.hintType(hasLocalDescription() ? Description::Type::Answer
-	                                           : Description::Type::Offer);
+	// Get the new signaling state
+	SignalingState signalingState = mSignalingState.load();
+	SignalingState newSignalingState;
+	switch (signalingState) {
+	case SignalingState::Stable:
+		description.hintType(Description::Type::Offer);
+		if (description.type() != Description::Type::Offer) {
+			LOG_ERROR << "Unexpected remote " << description.type()
+			          << " description in signaling state " << signalingState << ", expected offer";
+			std::ostringstream oss;
+			oss << "Unexpected remote " << description.type() << " description";
+			throw std::logic_error(oss.str());
+		}
+		newSignalingState = SignalingState::HaveRemoteOffer;
+		break;
 
-	// If there is no remote description, this is the first negociation
-	// Check it is what we expect
-	if (!hasRemoteDescription()) {
-		if (description.type() == Description::Type::Offer) {
-			if (hasLocalDescription()) {
-				PLOG_ERROR << "Got a remote offer description while an answer was expected";
-				throw std::logic_error("Got an unexpected remote offer description");
-			}
-		} else { // Answer
-			PLOG_ERROR << "Got a remote answer description while an offer was expected";
-			throw std::logic_error("Got an unexpected remote answer description");
+	case SignalingState::HaveLocalOffer:
+	case SignalingState::HaveRemotePranswer:
+		description.hintType(Description::Type::Answer);
+		if (description.type() != Description::Type::Answer ||
+		    description.type() != Description::Type::Pranswer) {
+			LOG_ERROR << "Unexpected remote " << description.type()
+			          << " description in signaling state " << signalingState
+			          << ", expected answer";
+			std::ostringstream oss;
+			oss << "Unexpected remote " << description.type() << " description";
+			throw std::logic_error(oss.str());
 		}
+		newSignalingState = SignalingState::Stable;
+		break;
+
+	default:
+		LOG_ERROR << "Unexpected remote description in signaling state " << signalingState;
+		throw std::logic_error("Unexpected remote description");
 	}
 
 	// Candidates will be added at the end, extract them for now
@@ -168,12 +207,19 @@ void PeerConnection::setRemoteDescription(Description description) {
 		iceTransport = initIceTransport(Description::Role::ActPass);
 	iceTransport->setRemoteDescription(description);
 
+	if (description.hasApplication()) {
+		if (auto current = remoteDescription(); current && !current->hasApplication())
+			if (auto dtlsTransport = std::atomic_load(&mDtlsTransport);
+			    dtlsTransport && dtlsTransport->state() == Transport::State::Connected)
+				initSctpTransport();
+	}
+
 	{
 		// Set as remote description
 		std::lock_guard lock(mRemoteDescriptionMutex);
-		
+
 		std::vector<Candidate> existingCandidates;
-		if(mRemoteDescription)
+		if (mRemoteDescription)
 			existingCandidates = mRemoteDescription->extractCandidates();
 
 		mRemoteDescription.emplace(std::move(description));
@@ -181,11 +227,12 @@ void PeerConnection::setRemoteDescription(Description description) {
 			mRemoteDescription->addCandidate(candidate);
 	}
 
+	changeSignalingState(newSignalingState);
+
 	if (description.type() == Description::Type::Offer) {
 		// This is an offer, we need to answer
-		Description localDescription = iceTransport->getLocalDescription(Description::Type::Answer);
-		processLocalDescription(localDescription);
-		iceTransport->gatherLocalCandidates();
+		mNegociationNeeded = true;
+		setLocalDescription(Description::Type::Answer);
 	} else {
 		// This is an answer
 		auto sctpTransport = std::atomic_load(&mSctpTransport);
@@ -204,6 +251,8 @@ void PeerConnection::setRemoteDescription(Description description) {
 			}
 			std::swap(mDataChannels, newDataChannels);
 		}
+
+		changeSignalingState(SignalingState::Stable);
 	}
 
 	for (const auto &candidate : remoteCandidates)
@@ -261,7 +310,7 @@ shared_ptr<DataChannel> PeerConnection::addDataChannel(string label, string prot
 		if (transport->state() == SctpTransport::State::Connected)
 			channel->open(transport);
 
-	// Renegociation is needed if the current local description does not have application
+	// Renegociation is needed iff the current local description does not have application
 	std::lock_guard lock(mLocalDescriptionMutex);
 	if (!mLocalDescription || !mLocalDescription->hasApplication())
 		mNegociationNeeded = true;
@@ -297,6 +346,10 @@ void PeerConnection::onGatheringStateChange(std::function<void(GatheringState st
 	mGatheringStateChangeCallback = callback;
 }
 
+void PeerConnection::onSignalingStateChange(std::function<void(SignalingState state)> callback) {
+	mSignalingStateChangeCallback = callback;
+}
+
 std::shared_ptr<Track> PeerConnection::addTrack(Description::Media description) {
 	if (hasLocalDescription())
 		throw std::logic_error("Tracks must be created before local description");
@@ -401,7 +454,7 @@ shared_ptr<DtlsTransport> PeerConnection::initDtlsTransport() {
 
 			switch (state) {
 			case DtlsTransport::State::Connected:
-				if (auto local = localDescription(); local && local->hasApplication())
+				if (auto remote = remoteDescription(); remote && remote->hasApplication())
 					initSctpTransport();
 				else
 					changeState(State::Connected);
@@ -833,9 +886,9 @@ void PeerConnection::processLocalDescription(Description description) {
 	{
 		// Set as local description
 		std::lock_guard lock(mLocalDescriptionMutex);
-			
+
 		std::vector<Candidate> existingCandidates;
-		if(mLocalDescription)
+		if (mLocalDescription)
 			existingCandidates = mLocalDescription->extractCandidates();
 
 		mLocalDescription.emplace(std::move(description));
@@ -910,6 +963,16 @@ bool PeerConnection::changeGatheringState(GatheringState state) {
 	return true;
 }
 
+bool PeerConnection::changeSignalingState(SignalingState state) {
+	if (mSignalingState.exchange(state) != state) {
+		std::ostringstream s;
+		s << state;
+		PLOG_INFO << "Changed signaling state to " << s.str();
+		mProcessor->enqueue([this, state] { mSignalingStateChangeCallback(state); });
+	}
+	return true;
+}
+
 void PeerConnection::resetCallbacks() {
 	// Unregister all callbacks
 	mDataChannelCallback = nullptr;
@@ -957,7 +1020,7 @@ std::optional<std::chrono::milliseconds> PeerConnection::rtt() {
 
 std::ostream &operator<<(std::ostream &out, const rtc::PeerConnection::State &state) {
 	using State = rtc::PeerConnection::State;
-	std::string str;
+	const char *str;
 	switch (state) {
 	case State::New:
 		str = "new";
@@ -986,13 +1049,13 @@ std::ostream &operator<<(std::ostream &out, const rtc::PeerConnection::State &st
 
 std::ostream &operator<<(std::ostream &out, const rtc::PeerConnection::GatheringState &state) {
 	using GatheringState = rtc::PeerConnection::GatheringState;
-	std::string str;
+	const char *str;
 	switch (state) {
 	case GatheringState::New:
 		str = "new";
 		break;
 	case GatheringState::InProgress:
-		str = "in_progress";
+		str = "in-progress";
 		break;
 	case GatheringState::Complete:
 		str = "complete";
@@ -1003,3 +1066,29 @@ std::ostream &operator<<(std::ostream &out, const rtc::PeerConnection::Gathering
 	}
 	return out << str;
 }
+
+std::ostream &operator<<(std::ostream &out, const rtc::PeerConnection::SignalingState &state) {
+	using SignalingState = rtc::PeerConnection::SignalingState;
+	const char *str;
+	switch (state) {
+	case SignalingState::Stable:
+		str = "stable";
+		break;
+	case SignalingState::HaveLocalOffer:
+		str = "have-local-offer";
+		break;
+	case SignalingState::HaveRemoteOffer:
+		str = "have-remote-offer";
+		break;
+	case SignalingState::HaveLocalPranswer:
+		str = "have-local-pranswer";
+		break;
+	case SignalingState::HaveRemotePranswer:
+		str = "have-remote-pranswer";
+		break;
+	default:
+		str = "unknown";
+		break;
+	}
+	return out << str;
+}